/*
* Scriptographer
*
* This file is part of Scriptographer, a Scripting Plugin for Adobe Illustrator
* http://scriptographer.org/
*
* Copyright (c) 2002-2010, Juerg Lehni
* http://scratchdisk.com/
*
* All rights reserved. See LICENSE file for details.
*
* File created on 08.04.2005.
*/
package com.scriptographer.ai;
import java.util.Iterator;
import java.util.Map;
import com.scratchdisk.util.AbstractMap;
import com.scratchdisk.util.IntMap;
/**
* @author lehni
*
* @jshide
*/
public class Dictionary extends AbstractMap<String, Object>
implements ValidationObject {
protected int handle;
protected Document document;
protected boolean release;
protected ValidationObject validation;
protected static IntMap<Dictionary> dictionaries =
new IntMap<Dictionary>();
protected Dictionary(int handle, Document document, boolean release,
ValidationObject validation) {
this.handle = handle;
this.document = document != null
? document : Document.getWorkingDocument();
this.release = release;
this.validation = validation;
dictionaries.put(handle, this);
}
protected Dictionary(int handle, boolean release,
ValidationObject validation) {
this(handle, Document.getWorkingDocument(), release, validation);
}
public Dictionary() {
// By default use the working document as the validation scope
this(nativeCreate(), true, Document.getWorkingDocument());
}
private static native int nativeCreate();
protected static native int nativeCreateLiveEffectParameters();
public Dictionary(Map<?, ?> map) {
this();
for (Map.Entry<?, ?> entry : map.entrySet())
put(entry.getKey().toString(), entry.getValue());
}
private native Object nativeGet(int handle, int docHandle, Object key);
public Object get(Object key) {
return nativeGet(handle, document.handle, key);
}
private native boolean nativePut(int handle, String key, Object value);
public Object put(String key, Object value) {
Object previous = get(key);
if (!nativePut(handle, key, value))
throw new IllegalArgumentException(
"Dictionaries do not support objects of type "
+ value.getClass().getSimpleName());
return previous;
}
private native boolean nativeRemove(int handle, Object key);
public Object remove(Object key) {
Object previous = get(key);
return nativeRemove(handle, key) ? previous : null;
}
public native boolean containsKey(Object key);
public native int size();
// TODO: instead of producing a full array for all keys, we could add
// support for iterators to AbstractMap as an alternative (supporting both),
// and implementing a wrapper for the native dictionary iterator here.
protected native String[] keys();
private native void nativeRelease(int handle);
protected boolean release() {
if (release && handle != 0) {
nativeRelease(handle);
handle = 0;
return true;
}
return false;
}
protected void finalize() {
if (release())
dictionaries.remove(handle);
}
public Document getDocument() {
return document;
}
public Object clone() {
return new Dictionary(this);
}
public boolean equals(Object obj) {
if (obj instanceof Map) {
Map map = (Map) obj;
Object[] keys2 = map.keySet().toArray();
if (keys2.length != this.size())
return false;
Object[] keys1 = this.keys();
for (int i = keys1.length - 1; i >= 0; i--) {
Object key1 = keys1[i];
Object key2 = keys2[i];
if (!key1.equals(key2))
return false;
Object value1 = this.get(key1);
Object value2 = map.get(key2);
if (value1 != value2 && (value1 == null || !value1.equals(value2)))
return false;
}
return true;
}
return super.equals(obj);
}
public int hashCode() {
return handle != 0 ? handle : super.hashCode();
}
public boolean isValid() {
// Also check validation object that this dictionary depends on.
if (validation != null && !validation.isValid())
return false;
return handle != 0;
}
/**
* Called from the native environment when a dictionary becomes part of
* another. This will create a validation chain that is checked in isValid,
* down to the originating native object, e.g. Item or Document.
*/
protected void setValidation(ValidationObject validation) {
this.validation = validation;
}
/**
* @jshide
*/
public String getId() {
return "@" + Integer.toHexString(hashCode());
}
public String toString() {
return getClass().getSimpleName() + " (" + getId() + ")";
}
protected static Dictionary wrapHandle(int handle, Document document,
ValidationObject validation) {
Dictionary dict = dictionaries.get(handle);
if (dict == null || document != null && dict.document != document) {
// Reused handle in a different document, set handle of old
// wrapper to 0 and produce a new one.
if (dict != null)
dict.handle = 0;
dict = new Dictionary(handle, document, true, validation);
}
return dict;
}
/**
* Called from the native environment to wrap a Dictionary:
*/
protected static Dictionary wrapHandle(int handle, int docHandle,
ValidationObject validation) {
return wrapHandle(handle, Document.wrapHandle(docHandle), validation);
}
/**
* Releases all dictionaries that were used since last executed, and sets
* their handles to 0 so existing references will be invalid.
*
* This needs to be executed at the end of each history cycle, as
* Illustrator produces very odd crashes when dictionary references to
* item dictionaries are kept alive to items that do not exist any longer
* due to undoing. Confusingly, these crashes happen either in
* AWS_CUI_RevertAlert or AWS_CUI_GetVersionComments.
*
* This is called automatically from ScriptographerEngine.endExecution().
*
* @jshide
*/
public static void releaseInvalid() {
// Release all invalid dictionaries and then clear the lookup table
Iterator<Dictionary> it = dictionaries.values().iterator();
while (it.hasNext()) {
Dictionary dict = it.next();
// Also get rid of cached dictionaries that do not need to release
// themselves, as we never know when they become invalid
if (dict.validation != null && !dict.validation.isValid()
&& dict.release() || !dict.release)
it.remove();
}
}
}