// Copyright (C) 2015 The Android Open Source Project // // 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. package com.google.gerrit.client.api; import com.google.gerrit.client.GerritUiExtensionPoint; import com.google.gerrit.client.rpc.Natives; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArray; import com.google.gwt.dom.client.Element; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.SimplePanel; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; public class ExtensionPanel extends FlowPanel { private static final Logger logger = Logger.getLogger(ExtensionPanel.class.getName()); private final GerritUiExtensionPoint extensionPoint; private final List<Context> contexts; public ExtensionPanel(GerritUiExtensionPoint extensionPoint) { this(extensionPoint, new ArrayList<String>()); } public ExtensionPanel(GerritUiExtensionPoint extensionPoint, List<String> panelNames) { this.extensionPoint = extensionPoint; this.contexts = create(panelNames); } private List<Context> create(List<String> panelNames) { List<Context> contexts = new ArrayList<>(); for (Definition def : getOrderedDefs(panelNames)) { SimplePanel p = new SimplePanel(); add(p); contexts.add(Context.create(def, p)); } return contexts; } private List<Definition> getOrderedDefs(List<String> panelNames) { if (panelNames == null) { panelNames = Collections.emptyList(); } Map<String, List<Definition>> defsOrderedByName = new LinkedHashMap<>(); for (String name : panelNames) { defsOrderedByName.put(name, new ArrayList<Definition>()); } for (Definition def : Natives.asList(Definition.get(extensionPoint.name()))) { addDef(def, defsOrderedByName); } List<Definition> orderedDefs = new ArrayList<>(); for (List<Definition> defList : defsOrderedByName.values()) { orderedDefs.addAll(defList); } return orderedDefs; } private static void addDef(Definition def, Map<String, List<Definition>> defsOrderedByName) { String panelName = def.getPanelName(); if (panelName.equals(def.getPluginName() + ".undefined")) { /* Handle a partially undefined panel name from the javascript layer by generating a random panel name. This maintains support for panels that do not provide a name. */ panelName = def.getPluginName() + "." + Long.toHexString(Double.doubleToLongBits(Math.random())); } if (defsOrderedByName.containsKey(panelName)) { defsOrderedByName.get(panelName).add(def); } else if (defsOrderedByName.containsKey(def.getPluginName())) { defsOrderedByName.get(def.getPluginName()).add(def); } else { defsOrderedByName.put(panelName, Collections.singletonList(def)); } } public void put(GerritUiExtensionPoint.Key key, String value) { for (Context ctx : contexts) { ctx.put(key.name(), value); } } public void putInt(GerritUiExtensionPoint.Key key, int value) { for (Context ctx : contexts) { ctx.putInt(key.name(), value); } } public void putBoolean(GerritUiExtensionPoint.Key key, boolean value) { for (Context ctx : contexts) { ctx.putBoolean(key.name(), value); } } public void putObject(GerritUiExtensionPoint.Key key, JavaScriptObject value) { for (Context ctx : contexts) { ctx.putObject(key.name(), value); } } @Override protected void onLoad() { super.onLoad(); for (Context ctx : contexts) { try { ctx.onLoad(); } catch (RuntimeException e) { logger.log( Level.SEVERE, "Failed to load extension panel for extension point " + extensionPoint.name() + " from plugin " + ctx.getPluginName() + ": " + e.getMessage()); } } } @Override protected void onUnload() { super.onUnload(); for (Context ctx : contexts) { for (JavaScriptObject u : Natives.asList(ctx.unload())) { ApiGlue.invoke(u); } } } static class Definition extends JavaScriptObject { static final JavaScriptObject TYPE = init(); private static native JavaScriptObject init() /*-{ function PanelDefinition(n, c, x) { this.pluginName = n; this.onLoad = c; this.name = x; }; return PanelDefinition; }-*/; static native JsArray<Definition> get(String i) /*-{ return $wnd.Gerrit.panels[i] || [] }-*/; protected Definition() {} public final native String getPanelName() /*-{ return this.pluginName + "." + this.name; }-*/; public final native String getPluginName() /*-{ return this.pluginName; }-*/; } static class Context extends JavaScriptObject { static final Context create(Definition def, SimplePanel panel) { return create(TYPE, def, panel.getElement()); } final native void onLoad() /*-{ this._d.onLoad(this) }-*/; final native JsArray<JavaScriptObject> unload() /*-{ return this._u }-*/; final native String getPluginName() /*-{ return this._d.pluginName; }-*/; final native void put(String k, String v) /*-{ this.p[k] = v; }-*/; final native void putInt(String k, int v) /*-{ this.p[k] = v; }-*/; final native void putBoolean(String k, boolean v) /*-{ this.p[k] = v; }-*/; final native void putObject(String k, JavaScriptObject v) /*-{ this.p[k] = v; }-*/; private static native Context create(JavaScriptObject T, Definition d, Element e) /*-{ return new T(d,e) }-*/ ; private static final JavaScriptObject TYPE = init(); private static native JavaScriptObject init() /*-{ var T = function(d,e) { this._d = d; this._u = []; this.body = e; this.p = {}; }; T.prototype = { onUnload: function(f){this._u.push(f)}, }; return T; }-*/; protected Context() {} } }