001: /*
002: * uDig - User Friendly Desktop Internet GIS client
003: * http://udig.refractions.net
004: * (C) 2004, Refractions Research Inc.
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: */
017: package net.refractions.udig.internal.ui;
018:
019: import java.io.Serializable;
020: import java.util.ArrayList;
021: import java.util.Arrays;
022: import java.util.Comparator;
023: import java.util.LinkedHashSet;
024: import java.util.List;
025: import java.util.Set;
026: import java.util.TreeSet;
027:
028: import net.refractions.udig.core.internal.ExtensionPointProcessor;
029: import net.refractions.udig.core.internal.ExtensionPointUtil;
030: import net.refractions.udig.ui.IDropAction;
031: import net.refractions.udig.ui.TransferFactory;
032:
033: import org.eclipse.core.runtime.CoreException;
034: import org.eclipse.core.runtime.IAdaptable;
035: import org.eclipse.core.runtime.IConfigurationElement;
036: import org.eclipse.core.runtime.IExtension;
037: import org.eclipse.core.runtime.IStatus;
038: import org.eclipse.core.runtime.Status;
039: import org.eclipse.swt.dnd.DropTargetEvent;
040: import org.eclipse.swt.dnd.Transfer;
041:
042: /**
043: * Processes drag and drop extensions.
044: * <p>
045: * The following extension points are processed:
046: * <ul>
047: * <li>net.refractions.udig.ui.dropTransfers</li>
048: * </ul>
049: * @author jones
050: * @since 1.0.0
051: */
052: public class UDIGDNDProcessor {
053:
054: private static TransferProcessor processor;
055:
056: /**
057: * Gets the transfers that are available in the current udig configurations.
058: * The transfers are order with the "known" UDIG transfers(UDIGByteAndLocalTransfer) as the first
059: * elements in the set, then the defined transfers and finally the "known"
060: * eclipse transfers(TextTransfer, FileTransfer, etc..)
061: *
062: * @return the transfers that are available in the current udig configurations.
063: */
064: public static Set<Transfer> getTransfers() {
065: if (processor == null) {
066: processor = new TransferProcessor();
067: ExtensionPointUtil.process(UiPlugin.getDefault(),
068: UiPlugin.DROP_TRANSFERS_ID, processor);
069: }
070: return new LinkedHashSet<Transfer>(processor.transfers);
071: }
072:
073: static class TransferProcessor implements ExtensionPointProcessor {
074:
075: TreeSet<Transfer> transfers = new TreeSet<Transfer>(
076: new TransferComparator());
077:
078: public void process(IExtension extension,
079: IConfigurationElement element) throws Exception {
080: Transfer[] tmp = ((TransferFactory) element
081: .createExecutableExtension("class")).getTransfers(); //$NON-NLS-1$
082: for (Transfer transfer : tmp) {
083: transfers.add(transfer);
084: }
085: }
086: }
087:
088: static class TransferComparator implements Comparator<Transfer>,
089: Serializable {
090:
091: /** long serialVersionUID field */
092: private static final long serialVersionUID = 1L;
093:
094: public int compare(Transfer o1, Transfer o2) {
095: if (o1 instanceof UDigByteAndLocalTransfer)
096: return -1;
097: if (o2 instanceof UDigByteAndLocalTransfer)
098: return 1;
099: if (o1 instanceof UDIGTransfer)
100: return -1;
101: if (o2 instanceof UDIGTransfer)
102: return 1;
103:
104: return -1;
105: }
106:
107: }
108:
109: public static class DropActionProcessor implements
110: ExtensionPointProcessor {
111:
112: Object data;
113: UDIGDropHandler handler;
114: List<IDropAction> actions;
115: private DropTargetEvent event;
116:
117: DropActionProcessor(Object data, UDIGDropHandler handler,
118: DropTargetEvent event) {
119: this .data = data;
120: this .handler = handler;
121: this .event = event;
122: actions = new ArrayList<IDropAction>();
123: }
124:
125: public void process(IExtension extension,
126: IConfigurationElement element) throws Exception {
127: EnablesFor enablesFor = new EnablesFor(element);
128: if (enablesFor.minimum < 1 && !enablesFor.expandable)
129: return;
130:
131: //first find a matching target
132: Object concreteTarget = findTarget(element);
133:
134: if (concreteTarget == null)
135: return;
136:
137: //next find a matching type
138: List<Object> concreteData = findData(element);
139: if (!concreteData.isEmpty()) {
140: try {
141: addActions(element, concreteTarget, concreteData,
142: enablesFor);
143: } catch (Throwable t) {
144: String msg = "Error validating drop action"; //$NON-NLS-1$
145: String ns = element.getNamespaceIdentifier();
146:
147: Status s = new Status(IStatus.WARNING, ns, 0, msg,
148: t);
149: UiPlugin.getDefault().getLog().log(s);
150: }
151: }
152:
153: }
154:
155: private void addActions(IConfigurationElement element,
156: Object concreteTarget, List<Object> concreteData,
157: EnablesFor enablesFor) throws CoreException {
158: if (enablesFor.minimum > concreteData.size())
159: return;
160: if (enablesFor.minimum == concreteData.size()) {
161: addAction(element, concreteTarget, concreteData);
162: return;
163: }
164:
165: if (enablesFor.expandable
166: && enablesFor.minimum < concreteData.size()) {
167: addAction(element, concreteTarget, concreteData);
168: return;
169: }
170: // if there is a set size the make a bunch of actions with that many items.
171: if (!enablesFor.expandable) {
172: List<Object> data = new ArrayList<Object>();
173: for (Object object : concreteData) {
174: if (data.size() < enablesFor.minimum) {
175: data.add(object);
176: } else {
177: addAction(element, concreteTarget, data);
178: data.clear();
179: data.add(object);
180: }
181: }
182:
183: if (data.size() == enablesFor.minimum) {
184: addAction(element, concreteTarget, data);
185: }
186: }
187: }
188:
189: private void addAction(IConfigurationElement element,
190: Object concreteTarget, List<Object> concreteData)
191: throws CoreException {
192: if (concreteData.isEmpty())
193: throw new IllegalArgumentException(
194: "Data cannot be null"); //$NON-NLS-1$
195: Object data;
196: if (concreteData.size() == 1) {
197: data = concreteData.get(0);
198: } else {
199: data = concreteData.toArray();
200: }
201: IDropAction action = (IDropAction) element
202: .createExecutableExtension("class"); //$NON-NLS-1$
203: action.init(element, event, handler.getViewerLocation(),
204: concreteTarget, data);
205: if (action.accept()) {
206: actions.add(action);
207: }
208: }
209:
210: private List<Object> findData(IConfigurationElement element) {
211: IConfigurationElement[] acceptedTypes = element
212: .getChildren("acceptedType"); //$NON-NLS-1$
213: List<Object> data = new ArrayList<Object>(Arrays
214: .asList((Object[]) this .data));
215:
216: Class<? extends Object> c = null;
217: final List<Object> concreteData = new ArrayList<Object>();
218:
219: for (int i = 0; i < acceptedTypes.length && !data.isEmpty(); i++) {
220: IConfigurationElement acceptedType = acceptedTypes[i];
221:
222: try {
223:
224: String clazz = acceptedType.getAttribute("class"); //$NON-NLS-1$
225:
226: c = loadClass(clazz, true);
227:
228: if (c == null)
229: continue;
230:
231: String adapt = acceptedType.getAttribute("adapt"); //$NON-NLS-1$
232: boolean doAdapt = "true".equals(adapt); //$NON-NLS-1$
233:
234: concreteData.addAll(processArray(data, c, doAdapt));
235: } catch (ClassNotFoundException e) {
236: //expected, do nothing
237: continue;
238: }
239: }
240:
241: return concreteData;
242: }
243:
244: private Class<? extends Object> loadClass(String clazz,
245: boolean isArray) throws ClassNotFoundException {
246: if (isArray) {
247: Object[] array = (Object[]) data;
248: for (Object object : array) {
249: Class<? extends Object> c;
250: c = getClassLoader(object).loadClass(clazz);
251: if (c != null)
252: return c;
253: }
254: return null;
255: } else {
256: return getClassLoader(data).loadClass(clazz);
257: }
258: }
259:
260: private List<Object> processArray(List<Object> data,
261: Class<? extends Object> c, boolean doAdapt) {
262:
263: List<Object> tmp = new ArrayList<Object>(data.size());
264: for (Object obj : data) {
265: Object d = getConcreteObject(obj, c, doAdapt);
266:
267: if (d != null) {
268: tmp.add(d);
269: }
270: }
271:
272: data.removeAll(tmp);
273: return tmp;
274: }
275:
276: private Object findTarget(IConfigurationElement element) {
277: IConfigurationElement[] targets = element
278: .getChildren("destination"); //$NON-NLS-1$
279: Object concreteTarget = null;
280: ClassLoader dloader = getClassLoader(handler.getTarget());
281: Class<? extends Object> c = null;
282:
283: for (int i = 0; i < targets.length && c == null; i++) {
284: IConfigurationElement target = targets[i];
285:
286: try {
287: String clazz = target.getAttribute("class"); //$NON-NLS-1$
288: c = dloader.loadClass(clazz);
289:
290: if (c == null)
291: continue;
292: String adapt = target.getAttribute("adapt"); //$NON-NLS-1$
293:
294: concreteTarget = getConcreteObject(handler
295: .getTarget(), c, "true".equals(adapt)); //$NON-NLS-1$
296: if (concreteTarget == null) {
297: c = null;
298: }
299: } catch (ClassNotFoundException e) {
300: //expected, do nothing
301: continue;
302: }
303: }
304:
305: return concreteTarget;
306: }
307:
308: private ClassLoader getClassLoader(Object data) {
309: ClassLoader sloader = data.getClass().getClassLoader();
310: if (sloader == null) {
311: //probably boot class loader
312: sloader = ClassLoader.getSystemClassLoader();
313: }
314: return sloader;
315: }
316:
317: private Object getConcreteObject(Object obj,
318: Class<? extends Object> desiredClass, boolean adapt) {
319:
320: if (desiredClass.isAssignableFrom(obj.getClass()))
321: return obj;
322: if (!adapt)
323: return null;
324: if (obj instanceof IAdaptable) {
325: IAdaptable adaptable = (IAdaptable) obj;
326: Object adapter = adaptable.getAdapter(desiredClass);
327: if (adapter != null)
328: return adapter;
329: }
330: return null;
331: }
332: }
333:
334: public static List<IDropAction> process(Object data,
335: UDIGDropHandler handler, DropTargetEvent event) {
336: if (data == null || handler == null
337: || handler.getTarget() == null)
338: return new ArrayList<IDropAction>();
339:
340: //process to see if anyone cares
341: DropActionProcessor d = new DropActionProcessor(data, handler,
342: event);
343: ExtensionPointUtil.process(UiPlugin.getDefault(),
344: IDropAction.XPID, d);
345:
346: return d.actions;
347: }
348:
349: private static class EnablesFor {
350:
351: int minimum;
352: boolean expandable;
353:
354: public EnablesFor(IConfigurationElement element) {
355: String enablesFor = element.getAttribute("enablesFor"); //$NON-NLS-1$
356: minimum = 1;
357: expandable = false;
358: if (enablesFor != null) {
359: if (enablesFor.contains("+")) { //$NON-NLS-1$
360: expandable = true;
361: enablesFor = enablesFor.substring(0,
362: enablesFor.indexOf('+')).trim();
363: }
364: if (enablesFor.trim().length() > 0) {
365: try {
366: minimum = Integer.valueOf(enablesFor);
367: } catch (NumberFormatException e) {
368: throw new NumberFormatException(
369: "enablesFor in DropAction: " + //$NON-NLS-1$
370: element.getName()
371: + " is not a number or a +. " + //$NON-NLS-1$
372: "See extension point for legal values."); //$NON-NLS-1$
373: }
374: }
375: }
376:
377: }
378: }
379: }
|