/*******************************************************************************
* CogTool Copyright Notice and Distribution Terms
* CogTool 1.3, Copyright (c) 2005-2013 Carnegie Mellon University
* This software is distributed under the terms of the FSF Lesser
* Gnu Public License (see LGPL.txt).
*
* CogTool is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* CogTool is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CogTool; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* CogTool makes use of several third-party components, with the
* following notices:
*
* Eclipse SWT version 3.448
* Eclipse GEF Draw2D version 3.2.1
*
* Unless otherwise indicated, all Content made available by the Eclipse
* Foundation is provided to you under the terms and conditions of the Eclipse
* Public License Version 1.0 ("EPL"). A copy of the EPL is provided with this
* Content and is also available at http://www.eclipse.org/legal/epl-v10.html.
*
* CLISP version 2.38
*
* Copyright (c) Sam Steingold, Bruno Haible 2001-2006
* This software is distributed under the terms of the FSF Gnu Public License.
* See COPYRIGHT file in clisp installation folder for more information.
*
* ACT-R 6.0
*
* Copyright (c) 1998-2007 Dan Bothell, Mike Byrne, Christian Lebiere &
* John R Anderson.
* This software is distributed under the terms of the FSF Lesser
* Gnu Public License (see LGPL.txt).
*
* Apache Jakarta Commons-Lang 2.1
*
* This product contains software developed by the Apache Software Foundation
* (http://www.apache.org/)
*
* jopt-simple version 1.0
*
* Copyright (c) 2004-2013 Paul R. Holser, Jr.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Mozilla XULRunner 1.9.0.5
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (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.mozilla.org/MPL/.
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The J2SE(TM) Java Runtime Environment version 5.0
*
* Copyright 2009 Sun Microsystems, Inc., 4150
* Network Circle, Santa Clara, California 95054, U.S.A. All
* rights reserved. U.S.
* See the LICENSE file in the jre folder for more information.
******************************************************************************/
package edu.cmu.cs.hcii.cogtool;
//import java.io.FileOutputStream;
//import java.io.PrintWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.swt.dnd.ByteArrayTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.dnd.TransferData;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import edu.cmu.cs.hcii.cogtool.model.AUndertaking;
import edu.cmu.cs.hcii.cogtool.model.Design;
import edu.cmu.cs.hcii.cogtool.model.Frame;
import edu.cmu.cs.hcii.cogtool.model.FrameElement;
import edu.cmu.cs.hcii.cogtool.model.FrameElementGroup;
import edu.cmu.cs.hcii.cogtool.model.Association;
import edu.cmu.cs.hcii.cogtool.model.Transition;
import edu.cmu.cs.hcii.cogtool.model.TransitionSource;
import edu.cmu.cs.hcii.cogtool.model.IWidget;
import edu.cmu.cs.hcii.cogtool.model.SimpleWidgetGroup;
import edu.cmu.cs.hcii.cogtool.model.Project;
import edu.cmu.cs.hcii.cogtool.model.WidgetAttributes;
import edu.cmu.cs.hcii.cogtool.util.ClipboardUtil;
import edu.cmu.cs.hcii.cogtool.util.ObjectLoader;
import edu.cmu.cs.hcii.cogtool.util.ObjectPersist;
import edu.cmu.cs.hcii.cogtool.util.ObjectSaver;
public class CogToolClipboard
{
/*
* Various "purposes" for serialization.
*/
public static final Object FilePersistence = Project.FILE_PERSISTENCE;
public static final String CopyTasks = AUndertaking.COPY_TASK_ONLY;
public static final String CopyDesigns = Design.COPY_ENTIRE_DESIGN;
public static final String CopyFrames = Frame.COPY_FRAME_ONLY;
public static final String CopyWidgets =
TransitionSource.COPY_TRANSITION_SOURCE_ONLY;
protected static class CogToolTransfer extends ByteArrayTransfer
{
/*
* Use long form name and Transfer.isSupportedType(TransferData) for
* platform isolation. Apple suggests using reverse-dns convention, but
* SWT (as of 3.1) uses deprecated "Scrap Manager" APIs that still use
* 4-char OSTypes. If we want to support x-application data transfer, we
* should switch to an OSType compatible name, e.g. "CgTl".
*/
// private static final String COGTOOL_NAME =
// "edu.cmu.cs.hcii.cogtool.CogTool";
private static final String COGTOOL_NAME = "CgTl";
private static final int COGTOOL_ID = registerType(COGTOOL_NAME);
private static CogToolTransfer ONLY = new CogToolTransfer();
private CogToolTransfer() { }
public static CogToolTransfer getInstance()
{
return ONLY;
}
@Override
public void javaToNative(Object object, TransferData transferData)
{
if ((object == null) || ! (object instanceof String)) {
return;
}
if (isSupportedType(transferData)) {
String xml = (String) object;
// write data to a byte array and then ask super to convert
ByteArrayOutputStream out =
new ByteArrayOutputStream(xml.length() * 2);
try {
PrintStream printer = new PrintStream(out, false, "UTF-8");
printer.print(xml);
}
catch (UnsupportedEncodingException e) {
// ignored
}
super.javaToNative(out.toByteArray(), transferData);
}
else {
System.err.println("Not isSupportedType()!" + object);
}
}
@Override
public Object nativeToJava(TransferData transferData)
{
if (isSupportedType(transferData)) {
byte[] buffer = (byte[]) super.nativeToJava(transferData);
if (buffer != null) {
ByteArrayInputStream in =
new ByteArrayInputStream(buffer);
try {
InputStreamReader reader =
new InputStreamReader(in, "UTF-8");
StringBuilder s = new StringBuilder();
int c;
while ((c = reader.read()) != -1) {
s.append((char) c);
}
return s.toString();
}
catch (UnsupportedEncodingException e) {
// ignored
}
catch (IOException e) {
throw new ClipboardUtil.ClipboardException("Fetch problem", e);
}
}
System.err.println("Nothing to paste!");
}
return null;
}
@Override
protected int[] getTypeIds()
{
return new int[] { COGTOOL_ID };
}
@Override
protected String[] getTypeNames()
{
return new String[] { COGTOOL_NAME };
}
}
protected static Transfer[] cogtoolClipboardTypes =
new Transfer[] { CogToolTransfer.getInstance() };
/**
* No actual object version is negative; we can use -1 to
* indicate a version saved only for the clipboard.
*/
protected static final int CLIPBOARD_VERSION = -1;
/**
* Object to represent which Project the objects were 'copied' from.
*/
public static class ProjectScope
{
protected Project projectScope;
protected static final String scopeVAR = "scope";
protected static ObjectSaver.IDataSaver<ProjectScope> SAVER =
new ObjectSaver.ADataSaver<ProjectScope>() {
@Override
public int getVersion()
{
return CLIPBOARD_VERSION;
}
@Override
public void saveData(ProjectScope v, ObjectSaver saver)
throws IOException
{
saver.saveString(v.projectScope.getUUID(),
scopeVAR);
}
};
public ProjectScope(Project scope)
{
projectScope = scope;
}
public Project getProject()
{
return projectScope;
}
public void clearProject()
{
projectScope = null;
}
}
public static final boolean SAVE_TO_CLIPBOARD = true;
public static final boolean SAVE_STRING_ONLY = false;
public static class ClipboardClassSaver extends ObjectSaver
{
protected boolean saveToClipboard;
public ClipboardClassSaver(Object saverPurpose, boolean toClipboard)
throws IOException
{
this(saverPurpose, null, toClipboard);
}
public ClipboardClassSaver(Object saverPurpose,
ISaverRegistry registry,
boolean toClipboard)
throws IOException
{
super(new StringWriter(), saverPurpose, registry);
saveToClipboard = toClipboard;
}
@Override
public void finish()
throws IOException
{
super.finish();
if (saveToClipboard) {
ClipboardUtil.copyDataToClipboard(sink.toString(),
cogtoolClipboardTypes);
}
}
public String getSavedString()
{
return sink.toString();
}
}
/**
* When 'copying' an Design to the clipboard, save the Tasks associated
* with the TaskApplication instances by their full path names.
* When 'pasting' these ITaskApplications, if the project is the same,
* the names will be used to look up instances; if not found or the project
* is not the same, the leaf name will be used to create a new Task.
*/
protected static class DesignClipboardSaverRegistry
extends ObjectSaver.OverrideSaverRegistry
{
public DesignClipboardSaverRegistry()
{
super(ObjectSaver.DEFAULT_REGISTRY);
registerSaver(ProjectScope.class.getName(), ProjectScope.SAVER);
}
}
/**
* Since there is no state, only one of these is required.
*/
protected static ObjectSaver.ISaverRegistry designClipboardSaverRegistry =
new DesignClipboardSaverRegistry();
/**
* When 'pasting' an Design, if an Project scope is given (i.e., not
* </code>null</code>), then look up Task instances instead of creating
* new ones. Create a new one if, for some reason, the look-up fails
* (e.g., the task was deleted between the 'copy' and the 'paste').
*/
protected static class ClipboardLoaderRegistry
extends ObjectLoader.OverrideLoaderRegistry
{
protected ProjectScope taskScope;
protected void checkTaskScope(String uuid)
{
Project taskScopeProject = taskScope.getProject();
if ((taskScopeProject == null) ||
! taskScopeProject.getUUID().equals(uuid))
{
taskScope.clearProject();
}
}
private ObjectLoader.IObjectLoader<ProjectScope> ProjectScope_Loader =
new ObjectLoader.AObjectLoader<ProjectScope>() {
@Override
public ProjectScope createObject()
{
return taskScope;
}
@Override
public void set(ProjectScope target, String variable, Object value)
{
if (variable != null) {
if (variable.equals(ProjectScope.scopeVAR)) {
checkTaskScope((String) value);
}
}
}
};
public ClipboardLoaderRegistry(Project scope)
{
super(ObjectLoader.DEFAULT_REGISTRY);
taskScope = new ProjectScope(scope);
registerLoader(ProjectScope.class.getName(),
CLIPBOARD_VERSION,
ProjectScope_Loader);
}
}
protected static class SelectedDesignSaver extends ClipboardClassSaver
{
public SelectedDesignSaver(boolean toClipboard)
throws IOException
{
super(CogToolClipboard.CopyDesigns,
designClipboardSaverRegistry,
toClipboard);
}
}
protected static class SelectedFrameSaver extends ClipboardClassSaver
{
protected Set<Frame> selectedFrames = new HashSet<Frame>();
protected Set<Transition> safeTransitions = new HashSet<Transition>();
public SelectedFrameSaver(Object saverPurpose,
Frame[] selection,
boolean toClipboard)
throws java.io.IOException
{
super(saverPurpose, toClipboard);
if (selection != null) {
for (Frame element : selection) {
selectedFrames.add(element);
}
}
}
// ATransitionSource ignores the return value.
@Override
public Object filterObject(Object value)
throws java.io.IOException
{
if (value instanceof Transition) {
if (! safeTransitions.contains(value)) {
Transition t = (Transition) value;
if (selectedFrames.contains(t.getDestination()) &&
selectedFrames.contains(t.getSource().getFrame()))
{
safeTransitions.add(t);
}
}
return null;
}
return super.filterObject(value);
}
@Override
public void finish()
throws java.io.IOException
{
Iterator<Transition> transitions = safeTransitions.iterator();
while (transitions.hasNext()) {
Transition t = transitions.next();
saveObject(t);
}
super.finish();
}
}
protected static class SelectedElementSaver extends ClipboardClassSaver
{
protected Set<FrameElement> selectedElements =
new HashSet<FrameElement>();
protected Map<SimpleWidgetGroup, SimpleWidgetGroup> twinnedGroups =
new HashMap<SimpleWidgetGroup, SimpleWidgetGroup>();
public SelectedElementSaver(Object saverPurpose,
FrameElement[] selection,
boolean toClipboard)
throws java.io.IOException
{
super(saverPurpose, toClipboard);
if (selection != null) {
selectedElements.addAll(Arrays.asList(selection));
}
}
/**
* Indicates whether the given remote label owner is itself selected.
*/
protected boolean isRemoteLabelOwnerSelected(FrameElement elt)
{
// If a parent widget group, then a member must be selected
if (elt instanceof SimpleWidgetGroup) {
Iterator<?> members = ((SimpleWidgetGroup) elt).iterator();
while (members.hasNext()) {
if (selectedElements.contains(members.next())) {
return true;
}
}
}
else {
// Otherwise, the element is either an IWidget or
// an FrameElementGroup, which must be selected
return selectedElements.contains(elt);
}
return false;
}
/**
* If a remote label widget whose owner element is not part of
* the selection, then temporarily remove the REMOTE_LABEL_OWNER_ATTR
* attribute while saving.
*/
@Override
public void saveObject(Object value)
throws java.io.IOException
{
IWidget asRemoteLabel = null; // null indicates not a remote label
FrameElement remoteLabelOwner = null;
if (value instanceof IWidget) {
asRemoteLabel = (IWidget) value; // while we check for attr
remoteLabelOwner =
(FrameElement)
asRemoteLabel.getAttribute(WidgetAttributes.REMOTE_LABEL_OWNER_ATTR);
// If not a remote label or the owner is selected, unset
if ((remoteLabelOwner == null) ||
isRemoteLabelOwnerSelected(remoteLabelOwner))
{
asRemoteLabel = null;
}
else {
// Otherwise, temporarily unset
asRemoteLabel.unsetAttribute(WidgetAttributes.REMOTE_LABEL_OWNER_ATTR);
}
}
// Save the object
super.saveObject(value);
// Re-establish the owner attribute if necessary
if (asRemoteLabel != null) {
asRemoteLabel.setAttribute(WidgetAttributes.REMOTE_LABEL_OWNER_ATTR,
remoteLabelOwner);
}
}
protected boolean isMemberOfSelectedGroup(FrameElement elt)
{
FrameElement rootElt = elt.getRootElement();
Iterator<FrameElementGroup> eltGroups =
rootElt.getEltGroups().iterator();
// If a selected item is a member of an FrameElementGroup
// that is also selected, skip it
while (eltGroups.hasNext()) {
if (selectedElements.contains(eltGroups.next())) {
return true;
}
}
return false;
}
// During copying to the clipboard, anonymous parent groups that are
// not themselves copied as part of selected element groups must be
// twinned for:
// MenuHeader, GridButton, and IListBoxItem
@Override
public Object filterObject(Object value)
throws java.io.IOException
{
if (value instanceof SimpleWidgetGroup) {
SimpleWidgetGroup valueGroup = (SimpleWidgetGroup) value;
if (! isMemberOfSelectedGroup(valueGroup)) {
SimpleWidgetGroup replacementGroup =
twinnedGroups.get(valueGroup);
if (replacementGroup == null) {
replacementGroup = valueGroup.twin();
twinnedGroups.put(valueGroup, replacementGroup);
Iterator<IWidget> groupWidgets =
valueGroup.iterator();
while (groupWidgets.hasNext()) {
IWidget widget = groupWidgets.next();
if (selectedElements.contains(widget)) {
replacementGroup.simpleAddWidget(widget);
}
}
}
return replacementGroup;
}
}
else if (value instanceof Set<?>) {
Set<Association<?>> replacement = null;
Iterator<?> elements = ((Set<?>) value).iterator();
while (elements.hasNext()) {
Object element = elements.next();
if (element instanceof FrameElementGroup) {
FrameElementGroup group = (FrameElementGroup) element;
if (replacement == null) {
replacement = new HashSet<Association<?>>();
}
if (selectedElements.contains(group)) {
replacement.add(group);
}
}
}
if (replacement != null) {
return replacement;
}
}
return super.filterObject(value);
}
}
// If toClipboard, finish() will save results to system clipboard
public static ClipboardClassSaver startClipboardSave(Object purpose,
boolean toClipboard)
throws IOException
{
return new ClipboardClassSaver(purpose, toClipboard);
}
public static ClipboardClassSaver startClipboardDesignSave(Project scope,
boolean toClipboard)
throws IOException
{
ClipboardClassSaver saver = new SelectedDesignSaver(toClipboard);
saver.saveObject(new ProjectScope(scope));
return saver;
}
public static ClipboardClassSaver startClipboardSave(Object purpose,
Frame[] selection,
boolean toClipboard)
throws IOException
{
return new SelectedFrameSaver(purpose, selection, toClipboard);
}
public static ClipboardClassSaver startClipboardSave(Object purpose,
FrameElement[] seln,
boolean toClipboard)
throws IOException
{
return new SelectedElementSaver(purpose, seln, toClipboard);
}
/**
* Checks the system clipboard for CogTool model objects.
* @return true if the clipboard contains CogTool model objects, else false
*/
public static boolean hasCogToolObjects()
{
return ClipboardUtil.checkTypes(CogToolTransfer.getInstance());
}
public static boolean hasNonTextPasteContent()
{
return hasCogToolObjects() || ClipboardUtil.hasImageData();
}
public static List<Object> loadCogToolObjects(String str, Project scope)
throws ParserConfigurationException, SAXException, IOException
{
if ((str == null) || ! str.startsWith("<" + ObjectPersist.PERSIST_ELT))
{
return null; // ? empty collection
}
ObjectLoader l = new ObjectLoader(new ClipboardLoaderRegistry(scope));
return l.load(new InputSource(new StringReader(str)), null);
}
public static List<Object> loadCogToolObjects(String str)
throws ParserConfigurationException, SAXException, IOException
{
return loadCogToolObjects(str, null);
}
public static List<Object> fetchCogToolObjects(Project scope)
throws ParserConfigurationException, SAXException, IOException
{
String str =
(String)
ClipboardUtil.fetchClipboardData(CogToolTransfer.getInstance());
return loadCogToolObjects(str, scope);
}
public static List<Object> fetchCogToolObjects()
throws ParserConfigurationException, SAXException, IOException
{
return fetchCogToolObjects(null);
}
}