/** * <copyright> * Copyright (c) 2010-2014 Henshin developers. 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 * </copyright> */ package org.eclipse.emf.henshin.model; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * <p> * This class represents an action of a {@link GraphElement}. Actions consist of: * <ul> * <li>an action type,</li> * <li>a Boolean multi flag,</li> * <li>a possibly empty path (only if the multi flag is <code>true</code>),</li> * <li>an optional fragment.</li> * </ul> * </p> * <p> * Since version 0.9.3, the syntax of actions is as follows: * ... * </p> * * <p> * Some basic examples (all examples shown with surrounding «..»): * <ul> * <li><span style="color:gray">«preserve»</span> Preserve a graph element.</li> * <li><span style="color:green">«create»</span> Create a graph element.</li> * <li><span style="color:red">«delete»</span> Delete a graph element.</li> * <li><span style="color:brown">«require»</span> Require the existence of a graph element.</li> * <li><span style="color:blue">«forbid»</span> Forbid the existence of a graph element.</li> * </ul> * </p> * * <p> * Examples of named positive and negative application conditions (PACs and NACs): * <ul> * <li><span style="color:brown">«require#1»</span> Graph element is part of a PAC named <i>1</i>.</li> * <li><span style="color:blue">«forbid#myNAC»</span> Graph is element of a NAC named <i>myNAC</i>.</li> * </ul> * </p> * * <p> * Examples of (nested) multi-rules: * <ul> * <li><span style="color:gray">«preserve*»</span> Preserve all matching graph elements (default multi-rule).</li> * <li><span style="color:red">«delete*/multi»</span> Delete all matching graph elements (multi-rule <i>multi</i>).</li> * <li><span style="color:green">«create*/my/nested/rule»</span> Create a graph element in a nested multi rule.</li> * <li><span style="color:brown">«require*/my/nested/rule#1»</span> Named PAC in a nested multi-rule.</li> * <li><span style="color:blue">«forbid*/my/nested/rule#myNAC»</span> Named NAC in a nested multi-rule.</li> * </ul> * </p> * * @author Christian Krause * @author Stefan Jurack */ public final class Action { /** * An enum for action types. * @author Christian Krause */ public static enum Type { // The following action types are supported: PRESERVE, CREATE, DELETE, FORBID, REQUIRE; /** * Parse an element action type. * @param value String representation of the action type. * @return The parsed action type. * @throws ParseException On parse errors. */ public static Type parse(String value) throws ParseException { value = value.trim(); for (Type type : values()) { if (type.name().equalsIgnoreCase(value)) return type; } // Some convenience... if ("remove".equalsIgnoreCase(value)) { return DELETE; } if ("new".equalsIgnoreCase(value)) { return CREATE; } if ("none".equalsIgnoreCase(value)) { return PRESERVE; } throw new ParseException("Unknown action type: " + value, 0); } /* * (non-Javadoc) * @see java.lang.Enum#toString() */ @Override public String toString() { return super.toString().toLowerCase(); } } /** * Separator for paths. */ public static final char PATH_SEPARATOR = '/'; /** * Multi-flag marker. */ public static final char MULTI_MARKER = '*'; /** * Fragment marker. */ public static final char FRAGMENT_START = '#'; // Empty string array. private static final String[] EMPTY_STRING_ARRAY = new String[0]; // Action type. private final Type type; // Multi flag. private final boolean isMulti; // Path. private final String[] path; // Fragment: private final String fragment; /** * Constructor. * @param type Action type. * @param isMulti Multi flag. * @param path Path. * @param fragment Fragment. */ public Action(Type type, boolean isMulti, String[] path, String fragment) { if (type==null) { throw new IllegalArgumentException("Action type must not be null."); } this.type = type; this.isMulti = isMulti; if (path!=null) { this.path = new String[path.length]; for (int i=0; i<path.length; i++) { this.path[i] = (path[i]!=null) ? path[i] : ""; } } else { this.path = EMPTY_STRING_ARRAY; } this.fragment = (fragment==null || fragment.trim().length()==0) ? null : fragment.trim(); } /** * Constructor without fragment. * @param type Action type. * @param isMulti Multi flag. * @param path Path. */ public Action(Type type, boolean isMulti, String[] path) { this(type, isMulti, path, null); } /** * Constructor without path and fragment. * @param type Action type. * @param isMulti Multi flag. */ public Action(Type type, boolean isMulti) { this(type, isMulti, null, null); } /** * Constructor without multi-flag, path and fragment. * @param type Action type. */ public Action(Type type) { this(type, false, null, null); } /** * Parses an action string for graph elements. * @param value String representation of the action. * @return The parsed element action. * @throws ParseException On parse errors. */ public static Action parse(String value) throws ParseException { // Null? if (value==null) { throw new NullPointerException(); } // Trim whitespace and convert to char array: char[] chars = value.trim().toCharArray(); // Empty string? if (chars.length==0) { throw new ParseException("Empty string", 0); } int pos = 0; // Parse the action type: String type = ""; while (pos<chars.length && Character.isLetter(chars[pos])) { type = type + chars[pos++]; } // Eat whitespace: while (pos<chars.length && Character.isWhitespace(chars[pos])) { pos++; } // Check for the multi-marker: boolean isMulti = false; if (pos<chars.length && chars[pos]==MULTI_MARKER) { isMulti = true; pos++; } // Eat whitespace: while (pos<chars.length && Character.isWhitespace(chars[pos])) { pos++; } // Check if there is a path: String[] path = EMPTY_STRING_ARRAY; if (pos<chars.length && chars[pos]==PATH_SEPARATOR) { pos++; List<String> pathList = new ArrayList<String>(); pathList.add(""); while (pos<chars.length) { if (Character.isJavaIdentifierPart(chars[pos])) { String newValue = pathList.get(pathList.size()-1) + chars[pos]; pathList.set(pathList.size()-1, newValue); } else if (chars[pos]==PATH_SEPARATOR) { pathList.add(""); } else { break; } pos++; } path = pathList.toArray(path); } // Check for the fragment: String fragment = null; if (pos<chars.length && chars[pos]==FRAGMENT_START) { fragment = ""; pos++; while (pos<chars.length && Character.isJavaIdentifierPart(chars[pos])) { fragment = fragment + chars[pos++]; } } // Now we MUST be at the end: if (pos<chars.length) { throw new ParseException("Unexpected character at position " + pos, pos); } // Create and return the new action: return new Action(Type.parse(type), isMulti, path, fragment); } /** * Returns the type of this action. This never returns <code>null</code>. * @return The action type. */ public Type getType() { return type; } /** * Get the multi flag. * @return Multi flag. */ public boolean isMulti() { return isMulti; } /** * Returns path this action contains. If no path was specified, this method * returns an empty string array. This never returns <code>null</code>. * @return The path. */ public String[] getPath() { return Arrays.copyOf(path , path.length); } /** * Returns the fragment of this action or <code>null</code> if it does not have a fragment. * @return The fragment or <code>null</code>. */ public String getFragment() { return fragment; } /** * Returns <code>true</code> if this action has the same type as the argument action. * @param action An action. * @return <code>true</code> if it has the same type. */ public boolean hasSameType(Action action) { return type==action.type; } /** * Returns <code>true</code> if this action has the same multi-flag as the argument action. * @param action An action. * @return <code>true</code> if it has the same multi-flag. */ public boolean hasSameMultiFlag(Action action) { return isMulti==action.isMulti; } /** * Returns <code>true</code> if this action has the same path as the argument action. * @param action An action. * @return <code>true</code> if it has the same path. */ public boolean hasSamePath(Action action) { return Arrays.equals(path, action.path); } /** * Returns <code>true</code> if this action has the same fragment as the argument action. * @param action An action. * @return <code>true</code> if it has the same fragment. */ public boolean hasSameFragment(Action action) { if (fragment==null) { return action.fragment==null; } return fragment.equals(action.fragment); } /* * (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { int hash = type.hashCode(); for (String elem : path) { hash = (hash + elem.hashCode()) << 1; } if (isMulti) { hash++; } if (fragment!=null) { hash += fragment.hashCode(); } return hash; } /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object object) { if (object==this) { return true; } if (object instanceof Action) { Action action = (Action) object; return (hasSameType(action) && hasSameMultiFlag(action) && hasSamePath(action) && hasSameFragment(action)); } return false; } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { StringBuffer result = new StringBuffer(); result.append(type.toString()); if (isMulti){ result.append(MULTI_MARKER); } if (path.length > 0) { for (int i=0; i<path.length; i++) { result.append(PATH_SEPARATOR); result.append(path[i]); } } if (fragment!=null) { result.append(FRAGMENT_START); result.append(fragment); } return result.toString(); } }