/**
* Copyright 2008 - 2015 The Loon Game Engine Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
* @project loon
* @author cping
* @email:javachenpeng@yahoo.com
* @version 0.5
*/
package loon.html5.gwt;
import java.nio.ByteBuffer;
import java.util.HashMap;
import loon.Assets;
import loon.LSystem;
import loon.Sound;
import loon.canvas.Image;
import loon.canvas.ImageImpl;
import loon.html5.gwt.preloader.Blob;
import loon.html5.gwt.preloader.PreloaderBundle;
import loon.jni.TypedArrayHelper;
import loon.jni.XDomainRequest;
import loon.jni.XDomainRequest.Handler;
import loon.utils.ObjectMap;
import loon.utils.Scale;
import loon.utils.TArray;
import loon.utils.reply.Function;
import loon.utils.reply.GoFuture;
import loon.utils.reply.GoPromise;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.canvas.dom.client.ImageData;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.dom.client.CanvasElement;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.ImageElement;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.resources.client.DataResource;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.resources.client.ResourcePrototype;
import com.google.gwt.typedarrays.shared.TypedArrays;
import com.google.gwt.user.client.Window;
import com.google.gwt.xhr.client.ReadyStateChangeHandler;
import com.google.gwt.xhr.client.XMLHttpRequest;
public class GWTAssets extends Assets {
public interface ImageManifest {
int[] imageSize(String path);
}
public void setImageManifest(ImageManifest manifest) {
imageManifest = manifest;
}
private final static String GWT_DEF_RES = "assets/";
private final static boolean LOG_XHR_SUCCESS = false;
private final GWTGame game;
private ImageManifest imageManifest;
private final HashMap<String, PreloaderBundle> clientBundles = new HashMap<String, PreloaderBundle>();
private Scale assetScale = null;
@Override
public void setPathPrefix(String prefix) {
if (!prefix.startsWith(GWT_DEF_RES)) {
pathPrefix = prefix;
}
}
public void addClientBundle(String regExp, PreloaderBundle clientBundle) {
clientBundles.put(regExp, clientBundle);
}
public void setAssetScale(float scaleFactor) {
this.assetScale = new Scale(scaleFactor);
}
@Override
public Image getImageSync(String path) {
if (game.gwtconfig != null && game.gwtconfig.asynResource) {
return getBundleImageSync(path);
}
for (Scale.ScaledResource rsrc : assetScale().getScaledResources(path)) {
return localImage(pathPrefix + path, rsrc.scale);
}
return new GWTImage(game.graphics(), new Throwable(
"Image missing from manifest: " + path));
}
@Override
public Image getImage(String path) {
if (game.gwtconfig != null && game.gwtconfig.asynResource) {
return getBundleImage(path);
}
Scale assetScale = (this.assetScale == null) ? Scale.ONE
: this.assetScale;
TArray<Scale.ScaledResource> rsrcs = assetScale
.getScaledResources(path);
return localImage(rsrcs.get(0).path, rsrcs.get(0).scale);
}
@Override
public Image getRemoteImage(String path) {
if (game.gwtconfig != null && game.gwtconfig.asynResource) {
return addBundleImage(path, Scale.ONE);
}
return localImage(path, Scale.ONE);
}
@Override
public Image getRemoteImage(String path, int width, int height) {
if (game.gwtconfig != null && game.gwtconfig.asynResource) {
return addBundleImage(path, Scale.ONE).preload(width, height);
}
return localImage(path, Scale.ONE).preload(width, height);
}
@Override
public Sound getSound(String path) {
if (game.gwtconfig != null && game.gwtconfig.asynResource) {
String url = pathPrefix + path;
PreloaderBundle clientBundle = getBundle(path);
if (clientBundle != null) {
String key = toKey(path);
DataResource resource = (DataResource) getResource(key,
clientBundle);
if (resource != null) {
url = resource.getSafeUri().asString();
}
} else {
url += ".mp3";
}
return new GWTSound(url);
}
path = getPath(path);
if (path.startsWith(LSystem.FRAMEWORK_IMG_NAME)) {
path = GWT_DEF_RES + path;
}
GWTResourcesLoader gwtFile = Loon.self.resources.internal(path);
return new GWTSound(gwtFile.path());
}
@Override
public String getTextSync(String path) throws Exception {
path = getPath(path);
if (path.startsWith(LSystem.FRAMEWORK_IMG_NAME)) {
path = GWT_DEF_RES + path;
}
GWTResourcesLoader gwtFile = Loon.self.resources.internal(path);
if (gwtFile.preloader.isText(path)) {
return gwtFile.readString();
}
ObjectMap<String, String> res = gwtFile.preloader.texts;
String tmp = res.get(path = gwtFile.path());
if (tmp == null
&& (path.indexOf('\\') != -1 || path.indexOf('/') != -1)) {
tmp = res.get(path.substring(path.indexOf('/') + 1, path.length()));
}
if (tmp == null
&& (path.indexOf('\\') != -1 || path.indexOf('/') != -1)) {
tmp = res.get(LSystem.getFileName(path = gwtFile.path()));
}
if (tmp == null) {
tmp = res.get(LSystem.getFileName(path = (GWT_DEF_RES + path)));
}
if (tmp == null) {
game.log().warn("file " + path + " not found");
}
return tmp;
}
@Override
public GoFuture<String> getText(String path) {
GoPromise<String> result = GoPromise.create();
path = getPath(path);
if (path.startsWith(LSystem.FRAMEWORK_IMG_NAME)) {
path = GWT_DEF_RES + path;
}
try {
return doXhr(path, XMLHttpRequest.ResponseType.Default).map(
new Function<XMLHttpRequest, String>() {
public String apply(XMLHttpRequest xhr) {
return xhr.getResponseText();
}
});
} catch (JavaScriptException e) {
if (Window.Navigator.getUserAgent().indexOf("MSIE") != -1) {
return doXdr(path).map(new Function<XDomainRequest, String>() {
public String apply(XDomainRequest xdr) {
return xdr.getResponseText();
}
});
} else {
GWTResourcesLoader gwtFile = Loon.self.resources.internal(path);
if (gwtFile.preloader.isText(path)) {
try {
result.succeed(gwtFile.readString());
} catch (Exception ex) {
result.succeed(null);
}
} else {
ObjectMap<String, String> res = gwtFile.preloader.texts;
String tmp = res.get(path = gwtFile.path());
if (tmp == null
&& (path.indexOf('\\') != -1 || path.indexOf('/') != -1)) {
tmp = res.get(path.substring(path.indexOf('/') + 1,
path.length()));
}
if (tmp == null
&& (path.indexOf('\\') != -1 || path.indexOf('/') != -1)) {
tmp = res
.get(LSystem.getFileName(path = gwtFile.path()));
}
if (tmp == null) {
tmp = res.get(LSystem
.getFileName(path = (GWT_DEF_RES + path)));
}
if (tmp == null) {
game.log().warn("file " + path + " not found");
}
try {
result.succeed(tmp);
} catch (Exception ex) {
result.succeed(null);
}
}
}
}
return result;
}
@Override
public GoFuture<byte[]> getBytes(final String path) {
String fullpath = getPath(path);
if (fullpath.startsWith(LSystem.FRAMEWORK_IMG_NAME)) {
fullpath = GWT_DEF_RES + path;
}
if (!TypedArrays.isSupported()) {
final GoPromise<byte[]> result = GoPromise.create();
try {
result.succeed(getBytesSync(path));
} catch (Exception ex) {
result.fail(new UnsupportedOperationException(
"TypedArrays not supported by this browser."));
}
return result;
}
try {
return doXhr(fullpath, XMLHttpRequest.ResponseType.ArrayBuffer)
.map(new Function<XMLHttpRequest, byte[]>() {
public byte[] apply(XMLHttpRequest xhr) {
ByteBuffer buffer = TypedArrayHelper.wrap(xhr
.getResponseArrayBuffer());
byte[] arr = new byte[buffer.remaining()];
buffer.get(arr);
buffer.position(0);
return arr;
}
});
} catch (Exception ex) {
final GoPromise<byte[]> result = GoPromise.create();
try {
result.succeed(getBytesSync(path));
} catch (Exception exc) {
return null;
}
return result;
}
}
@Override
public byte[] getBytesSync(String path) throws Exception {
path = getPath(path);
if (path.startsWith(LSystem.FRAMEWORK_IMG_NAME)) {
path = GWT_DEF_RES + path;
}
GWTResourcesLoader gwtFile = Loon.self.resources.internal(path);
if (gwtFile.preloader.isBinary(path)) {
return gwtFile.readBytes();
}
ObjectMap<String, Blob> res = gwtFile.preloader.binaries;
Blob tmp = res.get(path = gwtFile.path());
if (tmp == null
&& (path.indexOf('\\') != -1 || path.indexOf('/') != -1)) {
tmp = res.get(path.substring(path.indexOf('/') + 1, path.length()));
}
if (tmp == null
&& (path.indexOf('\\') != -1 || path.indexOf('/') != -1)) {
tmp = res.get(LSystem.getFileName(path = gwtFile.path()));
}
if (tmp == null) {
tmp = res.get(LSystem.getFileName(path = (GWT_DEF_RES + path)));
}
if (tmp == null) {
game.log().warn("file " + path + " not found");
}
return Loon.self.resources.internal(path).readBytes();
}
@Override
protected ImageImpl.Data load(String path) throws Exception {
path = getPath(path);
if (path.startsWith(LSystem.FRAMEWORK_IMG_NAME)) {
path = GWT_DEF_RES + path;
}
Exception error = null;
for (Scale.ScaledResource rsrc : assetScale().getScaledResources(path)) {
try {
ImageElement image = localImageElement(path);
Scale viewScale = game.graphics().scale(), imageScale = rsrc.scale;
float viewImageRatio = viewScale.factor / imageScale.factor;
if (viewImageRatio < 1f) {
ImageData data = GWTImage.scaleImage(image, viewImageRatio);
ImageElement img = Document.get().createImageElement();
img.setWidth(data.getWidth());
img.setHeight(data.getHeight());
image = img;
imageScale = viewScale;
}
return new ImageImpl.Data(imageScale, image, image.getWidth(),
image.getHeight());
} catch (Exception fnfe) {
error = fnfe;
}
}
game.log().warn(
"Could not load image: " + path + " [error=" + error + "]");
throw error != null ? error : new Exception(path);
}
@Override
protected ImageImpl createImage(boolean async, int rwid, int rhei,
String source) {
ImageElement img = Document.get().createImageElement();
img.setSrc(source);
return new GWTImage(game.graphics(), game.graphics().scale(), img,
source);
}
GWTAssets(GWTGame game) {
super(game.asyn());
this.game = game;
if (game.gwtconfig != null && game.gwtconfig.asynResource) {
GWTAssets.pathPrefix = GWT.getModuleBaseForStaticFiles()
+ "assets/";
} else {
GWTAssets.pathPrefix = "";
}
}
private Scale assetScale() {
return (assetScale != null) ? assetScale : game.graphics().scale();
}
private GoFuture<XDomainRequest> doXdr(final String path) {
final GoPromise<XDomainRequest> result = GoPromise.create();
XDomainRequest xdr = XDomainRequest.create();
xdr.setHandler(new Handler() {
@Override
public void onTimeout(XDomainRequest xdr) {
game.log().error("xdr::onTimeout[" + path + "]()");
result.fail(new Exception("Error getting " + path + " : "
+ xdr.getStatus()));
}
@Override
public void onProgress(XDomainRequest xdr) {
if (LOG_XHR_SUCCESS) {
game.log().debug("xdr::onProgress[" + path + "]()");
}
}
@Override
public void onLoad(XDomainRequest xdr) {
if (LOG_XHR_SUCCESS) {
game.log().debug("xdr::onLoad[" + path + "]()");
}
result.succeed(xdr);
}
@Override
public void onError(XDomainRequest xdr) {
game.log().error("xdr::onError[" + path + "]()");
result.fail(new Exception("Error getting " + path + " : "
+ xdr.getStatus()));
}
});
if (LOG_XHR_SUCCESS) {
game.log().debug("xdr.open('GET', '" + path + "')...");
}
xdr.open("GET", path);
if (LOG_XHR_SUCCESS) {
game.log().debug("xdr.send()...");
}
xdr.send();
return result;
}
private GoFuture<XMLHttpRequest> doXhr(final String path,
final XMLHttpRequest.ResponseType responseType) {
final GoPromise<XMLHttpRequest> result = GoPromise.create();
XMLHttpRequest xhr = XMLHttpRequest.create();
if (LOG_XHR_SUCCESS) {
game.log().debug("xhr.open('GET', '" + path + "')...");
}
xhr.open("GET", path);
xhr.setResponseType(responseType);
xhr.setOnReadyStateChange(new ReadyStateChangeHandler() {
@Override
public void onReadyStateChange(XMLHttpRequest xhr) {
int readyState = xhr.getReadyState();
if (readyState == XMLHttpRequest.DONE) {
int status = xhr.getStatus();
if (status != 0 && (status < 200 || status >= 400)) {
game.log().error(
"xhr::onReadyStateChange[" + path + "]"
+ "(readyState = " + readyState
+ "; status = " + status + ")");
result.fail(new Exception("Error getting " + path
+ " : " + xhr.getStatusText()));
} else {
if (LOG_XHR_SUCCESS)
game.log().debug(
"xhr::onReadyStateChange[" + path + "]"
+ "(readyState = " + readyState
+ "; status = " + status + ")");
result.succeed(xhr);
}
}
}
});
if (LOG_XHR_SUCCESS) {
game.log().debug("xhr.send()...");
}
xhr.send();
return result;
}
private GWTImage localImage(String path, Scale scale) {
path = getPath(path);
if (path.startsWith(LSystem.FRAMEWORK_IMG_NAME)) {
path = GWT_DEF_RES + path;
}
GWTResourcesLoader files = Loon.self.resources.internal(path);
if (files.preloader.isImage(path)) {
return new GWTImage(game.graphics(), scale,
files.preloader.images.get(path), path);
}
ObjectMap<String, ImageElement> res = files.preloader.images;
ImageElement tmp = res.get(path = files.path());
if (tmp == null
&& (path.indexOf('\\') != -1 || path.indexOf('/') != -1)) {
tmp = res.get(path.substring(path.indexOf('/') + 1, path.length()));
}
if (tmp == null
&& (path.indexOf('\\') != -1 || path.indexOf('/') != -1)) {
tmp = res.get(LSystem.getFileName(path = files.path()));
}
if (tmp == null) {
tmp = res.get(LSystem.getFileName(path = (GWT_DEF_RES + path)));
}
if (tmp == null) {
return getBundleImage(GWT.getModuleBaseForStaticFiles() + path,
scale);
}
return new GWTImage(game.graphics(), scale, tmp, path);
}
private ImageElement localImageElement(String path) {
path = getPath(path);
if (path.startsWith(LSystem.FRAMEWORK_IMG_NAME)) {
path = GWT_DEF_RES + path;
}
GWTResourcesLoader files = Loon.self.resources.internal(path);
if (files.preloader.isImage(path)) {
return files.preloader.images.get(path);
}
ObjectMap<String, ImageElement> res = files.preloader.images;
ImageElement tmp = res.get(path = files.path());
if (tmp == null
&& (path.indexOf('\\') != -1 || path.indexOf('/') != -1)) {
tmp = res.get(path.substring(path.indexOf('/') + 1, path.length()));
}
if (tmp == null
&& (path.indexOf('\\') != -1 || path.indexOf('/') != -1)) {
tmp = res.get(LSystem.getFileName(path = files.path()));
}
if (tmp == null) {
tmp = res.get(LSystem.getFileName(path = (GWT_DEF_RES + path)));
}
if (tmp == null) {
game.log().warn("file " + path + " not found");
}
return tmp;
}
private CanvasElement createEmptyCanvas(int w, int h) {
CanvasElement elem = Document.get().createCanvasElement();
elem.setWidth(w);
elem.setHeight(h);
Context2d context = elem.getContext2d();
context.setFillStyle("rgba(255,255,255,255)");
context.fillRect(0, 0, w, h);
return elem;
}
private native void setCrossOrigin(Element elem, String state) /*-{
if ('crossOrigin' in elem)
elem.setAttribute('crossOrigin', state);
}-*/;
private String toKey(String fullPath) {
String key = fullPath.substring(fullPath.lastIndexOf('/') + 1);
int dotCharIdx = key.indexOf('.');
return dotCharIdx != -1 ? key.substring(0, dotCharIdx) : key;
}
private ResourcePrototype getResource(String key,
PreloaderBundle clientBundle) {
ResourcePrototype resource = clientBundle.getResource(key);
return resource;
}
private PreloaderBundle getBundle(String collection) {
PreloaderBundle clientBundle = null;
for (HashMap.Entry<String, PreloaderBundle> entry : clientBundles
.entrySet()) {
String regExp = entry.getKey();
if (RegExp.compile(regExp).exec(collection) != null) {
clientBundle = entry.getValue();
}
}
return clientBundle;
}
protected GWTImage getBundleImage(String path, Scale scale) {
String url = pathPrefix + path;
PreloaderBundle clientBundle = getBundle(path);
if (clientBundle != null) {
String key = toKey(path);
ImageResource resource = (ImageResource) getResource(key,
clientBundle);
if (resource != null) {
url = resource.getSafeUri().asString();
}
}
return addBundleImage(url, scale);
}
private GWTImage addBundleImage(String url, Scale scale) {
ImageElement img = Document.get().createImageElement();
setCrossOrigin(img, "anonymous");
img.setSrc(url);
return new GWTImage(game.graphics(), scale, img, url);
}
public Image getBundleImage(String path) {
Scale assetScale = (this.assetScale == null) ? Scale.ONE
: this.assetScale;
TArray<Scale.ScaledResource> rsrcs = assetScale
.getScaledResources(path);
return getBundleImage(rsrcs.get(0).path, rsrcs.get(0).scale);
}
public Image getBundleImageSync(String path) {
if (imageManifest == null) {
throw new UnsupportedOperationException("getImageSync(" + path
+ ")");
} else {
for (Scale.ScaledResource rsrc : assetScale().getScaledResources(
path)) {
int[] size = imageManifest.imageSize(rsrc.path);
if (size == null)
continue;
return getBundleImage(rsrc.path, rsrc.scale).preload(size[0],
size[1]);
}
return new GWTImage(game.graphics(), new Throwable(
"Image missing from manifest: " + path));
}
}
}