blob: f3d438ce64ab058fff9fb2792dabc4bb76a33d1c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011 University of Illinois 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:
* Albert L. Rossi - design and implementation
******************************************************************************/
package org.eclipse.ptp.rm.jaxb.core.data.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.ptp.rm.jaxb.core.IAssign;
import org.eclipse.ptp.rm.jaxb.core.IJAXBNonNLSConstants;
import org.eclipse.ptp.rm.jaxb.core.IMatchable;
import org.eclipse.ptp.rm.jaxb.core.data.AttributeType;
import org.eclipse.ptp.rm.jaxb.core.data.MatchType;
import org.eclipse.ptp.rm.jaxb.core.data.PropertyType;
import org.eclipse.ptp.rm.jaxb.core.data.TargetType;
import org.eclipse.ptp.rm.jaxb.core.data.TestType;
import org.eclipse.ptp.rm.jaxb.core.messages.Messages;
import org.eclipse.ptp.rm.jaxb.core.utils.CoreExceptionUtils;
import org.eclipse.ptp.rm.jaxb.core.variables.RMVariableMap;
/**
* Wrapper implementation. A target contains any number of matches, with their
* associated actions, along with tests for conditional actions based on values
* of the target fields. <br>
* <br>
* There are two modes to matching. The default is to treat the matches as
* logically OR'd (like a SAT; <code>matchAll</code> = false). When the latter
* is set to true, the matches are taken as logically ANDed.<br>
* <br>
* The target can be a reference to a pre-existent Property or Attribute in the
* resource manager environment, or can be constructed when the match occurs.
* Dynamically constructed targets are added to a list during the tokenization,
* and then upon termination are merged according to property or attribute name,
* which is treated as a unique identifier. <br>
* <br>
* Tests are applied at the end of the tokenization. <br>
*
* @author arossi
*
*/
public class TargetImpl implements IMatchable, IJAXBNonNLSConstants {
private final String uuid;
private final String ref;
private final String type;
private final List<MatchImpl> matches;
private final List<TestImpl> tests;
private final List<Object> targets;
private Object refTarget;
private final boolean matchAll;
private boolean selected;
/**
* Wraps the Property or Attribute to be acted upon.
*
* @param uuid
* unique id associated with this resource manager operation (can
* be <code>null</code>).
* @param target
* JAXB data element
*/
public TargetImpl(String uuid, TargetType target) {
this.uuid = uuid;
ref = target.getRef();
type = target.getType();
matchAll = target.isMatchAll();
matches = new ArrayList<MatchImpl>();
List<MatchType> mdata = target.getMatch();
for (MatchType m : mdata) {
matches.add(new MatchImpl(uuid, m, this));
}
tests = new ArrayList<TestImpl>();
List<TestType> tdata = target.getTest();
for (TestType t : tdata) {
tests.add(new TestImpl(uuid, t));
}
targets = new ArrayList<Object>();
selected = false;
}
/**
* Applies the matches in order. If <code>matchAll</code> is in effect,
* already matched expressions are skipped until they are reset; the first
* match causes a return of this method.<br>
* <br>
* Upon match, the head of the segment up to the last character of the match
* is deleted.
*
* @param segment
* the current part of the stream to match
* @return whether a successful match was found on this target
* @throws CoreException
*/
public synchronized boolean doMatch(StringBuffer segment) throws Throwable {
int matched = 0;
boolean match = false;
for (MatchImpl m : matches) {
if (matchAll && m.getMatched()) {
matched++;
continue;
}
int tail = m.doMatch(segment.toString());
match = m.getMatched();
if (match) {
segment.delete(0, tail);
matched++;
selected = m.getMoveToTop();
break;
}
}
if (!matchAll || matched == matches.size()) {
for (MatchImpl m : matches) {
m.reset();
}
}
return match;
}
/**
* Get the target object for the given assign task.<br>
* <br>
* This method is called by the Match on its target parent. If the target is
* a reference to an existing object in the environment, this is then
* returned; else, the index counter for the assign task is retrieved,
* indicating where in the list of constructed targets it last was (the
* assumption is that an assign action is applied once to any given Property
* or Attribute), and this object is returned if it exists; in the case that
* the index is equal to or greater than the size of the list, a new target
* object is constructed and added to the list.
*
* @param assign
* action to be applied to target
* @return the appropriate target for this action
* @throws CoreException
*/
public Object getTarget(IAssign assign) throws CoreException {
if (refTarget != null) {
return refTarget;
}
Object target = null;
if (ref != null) {
RMVariableMap vmap = RMVariableMap.getActiveInstance();
String name = vmap.getString(uuid, ref);
target = vmap.get(name);
if (target == null) {
throw CoreExceptionUtils.newException(Messages.StreamParserNoSuchVariableError + name, null);
}
refTarget = target;
} else {
int i = assign.getIndex();
if (i < targets.size()) {
target = targets.get(i);
}
if (target == null) {
if (PROPERTY.equals(type)) {
PropertyType p = new PropertyType();
target = p;
targets.add(target);
} else if (ATTRIBUTE.equals(type)) {
AttributeType ja = new AttributeType();
target = ja;
targets.add(target);
} else {
throw CoreExceptionUtils.newException(Messages.StreamParserMissingTargetType + ref, null);
}
}
}
return target;
}
/**
* @return whether this target has been selected for promotion to the head
* of the list
*/
public boolean isSelected() {
return selected;
}
/**
* Called upon completion of tokenization.<br>
* <br>
* First merges any constructed targets, then applies the tests to all
* targets.
*
* @throws Throwable
*/
public synchronized void postProcess() throws Throwable {
if (refTarget == null) {
if (PROPERTY.equals(type)) {
mergeProperties(targets);
} else if (ATTRIBUTE.equals(type)) {
mergeAttributes(targets);
}
Map<String, Object> dmap = RMVariableMap.getActiveInstance().getDiscovered();
for (Object t : targets) {
for (TestImpl test : tests) {
test.setTarget(t);
test.doTest();
}
if (PROPERTY.equals(type)) {
dmap.put(((PropertyType) t).getName(), t);
} else if (ATTRIBUTE.equals(type)) {
dmap.put(((AttributeType) t).getName(), t);
}
}
targets.clear();
} else {
for (TestImpl test : tests) {
test.setTarget(refTarget);
test.doTest();
}
refTarget = null;
}
}
/**
* @param selected
* whether this target should be promoted to the head of the list
*/
public void setSelected(boolean selected) {
this.selected = selected;
}
/**
* Takes two attributes with the same name and merges the rest of their
* fields, such that non-<code>null</code> values replace <code>null</code>.
* An attempt to merge two non-<code>null</code> fields implies that there
* was an error in the tokenization logic, and an exception is thrown.
*
* @param previous
* Attribute
* @param current
* Attribute
* @throws Throwable
*/
private void merge(AttributeType previous, AttributeType current) throws Throwable {
Object v0 = previous.getValue();
Object v1 = current.getValue();
if (v0 == null) {
previous.setValue(v1);
} else if (v1 != null) {
throw new Throwable(Messages.StreamParserInconsistentPropertyWarning + v0 + CM + SP + v1);
}
String s0 = previous.getDefault();
String s1 = current.getDefault();
if (s0 == null) {
previous.setDefault(s1);
} else if (s1 != null) {
throw new Throwable(Messages.StreamParserInconsistentPropertyWarning + s0 + CM + SP + s1);
}
s0 = previous.getType();
s1 = current.getType();
if (s0 == null) {
previous.setType(s1);
} else if (s1 != null) {
throw new Throwable(Messages.StreamParserInconsistentPropertyWarning + s0 + CM + SP + s1);
}
s0 = previous.getStatus();
s1 = current.getStatus();
if (s0 == null) {
previous.setStatus(s1);
} else if (s1 != null) {
throw new Throwable(Messages.StreamParserInconsistentPropertyWarning + s0 + CM + SP + s1);
}
boolean b0 = previous.isReadOnly();
boolean b1 = current.isReadOnly();
if (!b0) {
previous.setReadOnly(b1);
}
b0 = previous.isVisible();
b1 = current.isVisible();
if (!b0) {
previous.setVisible(b1);
}
Integer i0 = previous.getMax();
Integer i1 = current.getMax();
if (i0 == null) {
previous.setMax(i1);
} else if (i1 != null) {
throw new Throwable(Messages.StreamParserInconsistentPropertyWarning + i0 + CM + SP + i1);
}
i0 = previous.getMin();
i1 = current.getMin();
if (i0 == null) {
previous.setMin(i1);
} else if (i1 != null) {
throw new Throwable(Messages.StreamParserInconsistentPropertyWarning + i0 + CM + SP + i1);
}
s0 = previous.getDescription();
s1 = current.getDescription();
if (s0 == null) {
previous.setDescription(s1);
} else if (s1 != null) {
throw new Throwable(Messages.StreamParserInconsistentPropertyWarning + s0 + CM + SP + s1);
}
s0 = previous.getChoice();
s1 = current.getChoice();
if (s0 == null) {
previous.setChoice(s1);
} else if (s1 != null) {
throw new Throwable(Messages.StreamParserInconsistentPropertyWarning + s0 + CM + SP + s1);
}
s0 = previous.getTooltip();
s1 = current.getTooltip();
if (s0 == null) {
previous.setTooltip(s1);
} else if (s1 != null) {
throw new Throwable(Messages.StreamParserInconsistentPropertyWarning + s0 + CM + SP + s1);
}
}
/**
* Takes two properties with the same name and merges the rest of their
* fields, such that non-<code>null</code> values replace <code>null</code>.
* An attempt to merge two non-<code>null</code> fields implies that there
* was an error in the tokenization logic, and an exception is thrown.
*
* @param previous
* Property
* @param current
* Property
* @throws Throwable
*/
private void merge(PropertyType previous, PropertyType current) throws Throwable {
Object v0 = previous.getValue();
Object v1 = current.getValue();
if (v0 == null) {
previous.setValue(v1);
} else if (v1 != null) {
throw new Throwable(Messages.StreamParserInconsistentPropertyWarning + v0 + CM + SP + v1);
}
String s0 = previous.getDefault();
String s1 = current.getDefault();
if (s0 == null) {
previous.setDefault(s1);
} else if (s1 != null) {
throw new Throwable(Messages.StreamParserInconsistentPropertyWarning + s0 + CM + SP + s1);
}
s0 = previous.getType();
s1 = current.getType();
if (s0 == null) {
previous.setType(s1);
} else if (s1 != null) {
throw new Throwable(Messages.StreamParserInconsistentPropertyWarning + s0 + CM + SP + s1);
}
boolean b0 = previous.isReadOnly();
boolean b1 = current.isReadOnly();
if (!b0) {
previous.setReadOnly(b1);
}
b0 = previous.isVisible();
b1 = current.isVisible();
if (!b0) {
previous.setVisible(b1);
}
}
/**
* Attributes are hashed against their name; attributes with the same name
* are merged into a single object. Nameless attributes may occur from an
* empty line at the end of the stream, and are simply discarded.
*
* @param targets
* list of targets constructed during tokenization
* @throws Throwable
*/
private void mergeAttributes(List<Object> targets) throws Throwable {
Map<String, AttributeType> hash = new HashMap<String, AttributeType>();
for (Iterator<Object> i = targets.iterator(); i.hasNext();) {
AttributeType current = (AttributeType) i.next();
String name = current.getName();
if (current.getName() == null) {
/*
* may be an artifact of end-of-stream; just throw it out
*/
i.remove();
continue;
}
AttributeType previous = hash.get(name);
if (previous != null) {
merge(previous, current);
i.remove();
} else {
hash.put(name, current);
}
}
}
/**
* Properties are hashed against their name; properties with the same name
* are merged into a single object. Nameless properties may occur from an
* empty line at the end of the stream, and are simply discarded.
*
* @param targets
* list of targets constructed during tokenization
* @throws Throwable
*/
private void mergeProperties(List<Object> targets) throws Throwable {
Map<String, PropertyType> hash = new HashMap<String, PropertyType>();
for (Iterator<Object> i = targets.iterator(); i.hasNext();) {
PropertyType current = (PropertyType) i.next();
String name = current.getName();
if (current.getName() == null) {
/*
* may be an artifact of end-of-stream; just throw it out
*/
i.remove();
continue;
}
PropertyType previous = hash.get(name);
if (previous != null) {
merge(previous, current);
i.remove();
} else {
hash.put(name, current);
}
}
}
}