content: /**
   *  Copyright (c) 2013-2016 Angelo ZERR.
   *  All rights reserved. This program and the accompanying materials
   *  are made available under the terms of the Eclipse Public License v1.0
   *  which accompanies this distribution, and is available at
   *  http://www.eclipse.org/legal/epl-v10.html
   *
   *  Contributors:
   *  Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
   */
  package tern.server.rhino;
  
  import java.io.IOException;
  import java.util.List;
  
  import org.mozilla.javascript.Context;
  import org.mozilla.javascript.Function;
  import org.mozilla.javascript.NativeObject;
  import org.mozilla.javascript.Scriptable;
  import org.mozilla.javascript.UniqueTag;
  
  import tern.ITernProject;
  import tern.TernException;
  import tern.server.AbstractScriptEngineTernServer;
  import tern.server.IResponseHandler;
  import tern.server.protocol.IJSONObjectHelper;
  import tern.server.protocol.TernDoc;
  import tern.server.protocol.html.ScriptTagRegion;
  import tern.server.rhino.loader.ClassPathScriptLoader;
  
  public class RhinoTernServer extends AbstractScriptEngineTernServer {
  
  	private Scriptable ternScope;
  
  	private final String[] BEFORE_SCRIPTS = { "env.rhino.1.2.js", "json.js" };
  	private final String[] AFTER_SCRIPTS = { "tern-server.js" };
  
  	public RhinoTernServer(ITernProject project) {
  		super(project);
  	}
  
  	@Override
  	public void addFile(String name, String text, ScriptTagRegion[] tags) {
  		TernDoc doc = new TernDoc();
  		doc.addFile(name, text, tags, null);
  		request(doc, new IResponseHandler() {
  			
  			@Override
  			public void onSuccess(Object data, String dataAsJsonString) {
  				//System.err.println(data);
  			}
  			
  			@Override
  			public void onError(String error, Throwable t) {
  				// TODO Auto-generated method stub
  				
  			}
  			
  			@Override
  			public boolean isDataAsJsonString() {
  				// TODO Auto-generated method stub
  				return true;
  			}
  		});
  	}
  
  	@Override
  	public void request(TernDoc doc, IResponseHandler handler) {
  		Context cx = Context.enter();
  		try {
  			// tern.js checks if file.text is typeof string
  			// set java primitive wrap to false, otherwise tern.js throws error
  			// ".files[n].text must be a string"
  			cx.getWrapFactory().setJavaPrimitiveWrap(false);
  
  			Scriptable ternScope = getTernSope();
  			Object jsObject = Context.javaToJS(doc.toString(), ternScope);
  			Object functionArgs[] = { jsObject, handler, handler.isDataAsJsonString() };
  			Object fObj = ((Scriptable)ternScope.get("server", ternScope)).get("request2", ternScope);
  			Function f = (Function) fObj;
  			f.call(cx, ternScope, ternScope, functionArgs);
  		} catch (Exception e) {
  			handler.onError(e.getMessage(), e);
  		} finally {
  			// Exit from the context.
  			Context.exit();
  		}
  	}
  
  	@Override
  	public IJSONObjectHelper getJSONObjectHelper() {
  		return RhinoJSONHelper.INSTANCE;
  	}
  
  	@Override
  	protected void doDispose() {
  		fireEndServer();
  	}
  
  	private Scriptable loadRhinoTern() throws TernException, IOException {
  
  		Scriptable ternScope;
  		Context cx = Context.enter();
  		try {
  
  			// Initialize the standard objects (Object, Function, etc.)
  			// This must be done before scripts can be executed. Returns
  			// a scope object that we use in later calls.
  			ternScope = cx.initStandardObjects();
  
  			// acorn uses self variables, define it here.
  			cx.evaluateString(ternScope, "var self = this;", "", 1, null);
  			cx.setOptimizationLevel(-1);
  
  			// Env scripts
  			loadScripts(cx, ternScope, BEFORE_SCRIPTS);
  
  			// Tern, acorn scripts
  			TernResources resources = loadTern();
  			// Load tern scripts (acorn + ternjs) + plugins scripts
  			List<TernResource> scripts = resources.getScripts();
  			for (TernResource script : scripts) {
  				cx.evaluateString(ternScope, script.getContent(), script.getFilename(), 0, null);
  			}
  			
  			// tern-server
  			String defs = resources.getDefsAsString();
  			loadScripts(cx, ternScope, AFTER_SCRIPTS);
  			StringBuilder script = new StringBuilder(
  					"var server = new J2V8TernServer(");
  			script.append("[");
  			script.append(defs.toString());
  			script.append("],");
  			script.append(getProject().getPlugins() != null ? getProject()
  					.getPlugins().toString() : "");
  			script.append(");");
  			cx.evaluateString(ternScope, script.toString(), "init", 0, null);
  			
  		} finally {
  			// Exit from the context.
  			Context.exit();
  		}
  		return ternScope;
  	}
  
  	private void loadScripts(Context cx, Scriptable ternScope, String[] srcs) throws IOException {
  		for (int i = 0; i < srcs.length; i++) {
  			ClassPathScriptLoader.getInstance().loadScript(cx, ternScope, srcs[i]);
  		}
  	}
  
  	private synchronized Scriptable getTernSope() throws TernException, IOException {
  		if (ternScope == null) {
  			ternScope = loadRhinoTern();
  		}
  		return ternScope;
  	}
  
  	private static class RhinoJSONHelper implements IJSONObjectHelper {
  
  		public static RhinoJSONHelper INSTANCE = new RhinoJSONHelper();
  
  		@SuppressWarnings("unchecked")
  		@Override
  		public Iterable<Object> getList(Object jsonObj, String name) {
  			Object result = ((NativeObject) jsonObj).get(name, // $NON-NLS-1$
  					(NativeObject) jsonObj);
  			return (result instanceof Iterable<?>) ? (Iterable<Object>) result : null;
  		}
  
  		@Override
  		public Long getCh(Object data, String name) {
  			Double d = null;
  			if (data instanceof Double) {
  				d = (Double) data;
  			} else {
  				Object loc = ((NativeObject) data).get(name, (NativeObject) data);
  				if (loc instanceof NativeObject) {
  					d = (Double) ((NativeObject)loc).get("ch", ((NativeObject)loc)); //$NON-NLS-1$
  				} else if (loc instanceof Double) {
  					d = (Double) loc;
  				}
  			}
  			if (d != null) {
  				return d.longValue();
  			}
  			return null;
  		}
  
  		@Override
  		public String getText(Object jsonObj, String property) {
  			Object text = ((Scriptable) jsonObj).get(property, (Scriptable) jsonObj);
  			if (text == null || (text instanceof UniqueTag)) {
  				return null;
  			}
  			return text.toString();
  		}
  
  		@Override
  		public Long getLong(Object jsonObject, String name) {
  			return null;
  		}
  
  		@Override
  		public boolean isString(Object value) {
  			return value instanceof String;
  		}
  
  		@Override
  		public String getText(Object value) {
  			if (value == null) {
  				return null;
  			}
  			return value.toString();
  		}
  
  		@Override
  		public boolean getBoolean(Object jsonObject, String name, boolean defaultValue) {
  			String val = getText(jsonObject, name);
  			return val != null ? Boolean.valueOf(val) : defaultValue;
  		}
  
  		@Override
  		public Object getObject(Object jsonObj, String name) {
  			return ((Scriptable) jsonObj).get(name, (Scriptable) jsonObj);
  		}
  
  	}
  }
  