/*
* ====================================================================
*
* The ObjectStyle Group Software License, Version 1.0
*
* Copyright (c) 2005 The ObjectStyle Group and individual authors of the
* software. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met: 1.
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer. 2. Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. 3. The end-user documentation
* included with the redistribution, if any, must include the following
* acknowlegement: "This product includes software developed by the ObjectStyle
* Group (http://objectstyle.org/)." Alternately, this acknowlegement may
* appear in the software itself, if and wherever such third-party
* acknowlegements normally appear. 4. The names "ObjectStyle Group" and
* "Cayenne" must not be used to endorse or promote products derived from this
* software without prior written permission. For written permission, please
* contact andrus@objectstyle.org. 5. Products derived from this software may
* not be called "ObjectStyle" nor may "ObjectStyle" appear in their names
* without prior written permission of the ObjectStyle Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* OBJECTSTYLE GROUP OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many individuals
* on behalf of the ObjectStyle Group. For more information on the ObjectStyle
* Group, please see <http://objectstyle.org/> .
*
*/
package org.objectstyle.wolips.bindings.wod;
import java.io.IOException;
import java.io.Writer;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.objectstyle.wolips.baseforplugins.util.ComparisonUtils;
import org.objectstyle.wolips.baseforplugins.util.StringUtilities;
import org.objectstyle.wolips.bindings.Activator;
import org.objectstyle.wolips.bindings.api.ApiCache;
import org.objectstyle.wolips.bindings.api.ApiModelException;
import org.objectstyle.wolips.bindings.api.ApiUtils;
import org.objectstyle.wolips.bindings.api.IApiBinding;
import org.objectstyle.wolips.bindings.api.Wo;
import org.objectstyle.wolips.bindings.preferences.PreferenceConstants;
import org.objectstyle.wolips.bindings.utils.BindingReflectionUtils;
/**
* @author mschrag
*/
public abstract class AbstractWodBinding implements IWodBinding {
private static Set<String> VALID_OGNL_VALUES = new HashSet<String>();
static {
AbstractWodBinding.VALID_OGNL_VALUES.add("and");
AbstractWodBinding.VALID_OGNL_VALUES.add("band");
AbstractWodBinding.VALID_OGNL_VALUES.add("bor");
AbstractWodBinding.VALID_OGNL_VALUES.add("eq");
AbstractWodBinding.VALID_OGNL_VALUES.add("gt");
AbstractWodBinding.VALID_OGNL_VALUES.add("gte");
AbstractWodBinding.VALID_OGNL_VALUES.add("in");
AbstractWodBinding.VALID_OGNL_VALUES.add("instanceof");
AbstractWodBinding.VALID_OGNL_VALUES.add("lt");
AbstractWodBinding.VALID_OGNL_VALUES.add("lte");
AbstractWodBinding.VALID_OGNL_VALUES.add("neq");
AbstractWodBinding.VALID_OGNL_VALUES.add("not");
AbstractWodBinding.VALID_OGNL_VALUES.add("null");
AbstractWodBinding.VALID_OGNL_VALUES.add("neq");
AbstractWodBinding.VALID_OGNL_VALUES.add("new");
AbstractWodBinding.VALID_OGNL_VALUES.add("or");
AbstractWodBinding.VALID_OGNL_VALUES.add("shl");
AbstractWodBinding.VALID_OGNL_VALUES.add("shr");
AbstractWodBinding.VALID_OGNL_VALUES.add("this");
AbstractWodBinding.VALID_OGNL_VALUES.add("ushr");
AbstractWodBinding.VALID_OGNL_VALUES.add("xor");
}
private boolean _validate;
public AbstractWodBinding() {
_validate = true;
}
public int compareTo(IApiBinding o) {
return (o == null) ? -1 : getName() == null ? -1 : getName().compareTo(o.getName());
}
@Override
public boolean equals(Object o) {
return o instanceof AbstractWodBinding && ComparisonUtils.equals(((AbstractWodBinding) o).getName(), getName());
}
@Override
public int hashCode() {
String name = getName();
return name == null ? 0 : name.hashCode();
}
public boolean isAction() {
return ApiUtils.isActionBinding(this);
}
public void setValidate(boolean validate) {
_validate = validate;
}
public boolean shouldValidate() {
return _validate;
}
public void writeWodFormat(Writer writer) throws IOException {
writer.write(" ");
if (getNamespace() != null) {
writer.write(getNamespace());
writer.write(":");
}
writer.write(getName());
writer.write(" = ");
writer.write(getValue());
writer.write(";");
}
public void writeInlineFormat(Writer writer, String prefix, String suffix) throws IOException {
writer.write(" ");
if (getNamespace() != null) {
writer.write(getNamespace());
writer.write(":");
}
writer.write(getName());
writer.write(" = ");
if (isLiteral()) {
writer.write(getValue());
}
else if (isOGNL()) {
writer.write("\"");
writer.write(getValue());
writer.write("\"");
}
else {
writer.write("\"");
writer.write(prefix);
writer.write(getValue());
writer.write(suffix);
writer.write("\"");
}
}
public boolean isTrueValue() {
String bindingValue = getValue();
return "true".equalsIgnoreCase(bindingValue) || "yes".equalsIgnoreCase(bindingValue);
}
public boolean isEmpty() {
String bindingValue = getValue();
return bindingValue == null || bindingValue.length() == 0;
}
public boolean isDigitsOnly() {
String bindingValue = getValue();
return !isEmpty() && StringUtilities.isNumericOnly(bindingValue);
}
public boolean isCaret() {
String bindingValue = getValue();
return bindingValue != null && bindingValue.startsWith("^");
}
public boolean isLiteral() {
String bindingValue = getValue();
return bindingValue != null && bindingValue.startsWith("\"");
}
public boolean isOGNL() {
String bindingValue = getValue();
return bindingValue != null && (bindingValue.startsWith("~") || bindingValue.startsWith("\"~"));
}
public boolean isKeyPath() {
return !isLiteral() && !isEmpty() && !isCaret() && !isOGNL() && !isDigitsOnly();
}
public abstract int getLineNumber();
public static List<WodProblem> getBindingProblems(String elementType, String keypath, IType javaFileType, TypeCache typeCache, HtmlElementCache htmlCache) throws JavaModelException, ApiModelException {
SimpleWodBinding binding = new SimpleWodBinding(null, "_temp", keypath);
return binding.getBindingProblems(elementType, javaFileType, typeCache, htmlCache);
}
public void fillInBindingProblems(IWodElement element, IApiBinding apiBinding, IJavaProject javaProject, IType javaFileType, List<WodProblem> problems, TypeCache cache, HtmlElementCache htmlCache) throws JavaModelException {
String missingCollectionSeverity = Activator.getDefault().getPluginPreferences().getString(PreferenceConstants.MISSING_COLLECTION_SEVERITY_KEY);
String missingComponentSeverity = Activator.getDefault().getPluginPreferences().getString(PreferenceConstants.MISSING_COMPONENT_SEVERITY_KEY);
String missingNSKVCSeverity = Activator.getDefault().getPluginPreferences().getString(PreferenceConstants.MISSING_NSKVC_SEVERITY_KEY);
String ambiguousSeverity = Activator.getDefault().getPluginPreferences().getString(PreferenceConstants.AMBIGUOUS_SEVERITY_KEY);
String atOperatorSeverity = Activator.getDefault().getPluginPreferences().getString(PreferenceConstants.AT_OPERATOR_SEVERITY_KEY);
String helperFunctionSeverity = Activator.getDefault().getPluginPreferences().getString(PreferenceConstants.HELPER_FUNCTION_SEVERITY_KEY);
if (shouldValidate()) {
String bindingNamespace = getNamespace();
String bindingName = getName();
String bindingValue = getValue();
boolean explicitlyValid = false;
String javaFileTypeName = javaFileType.getElementName();
if (javaFileTypeName != null) {
List<BindingValidationRule> bindingValidationRules = ApiCache.getBindingValidationRules();
for (int i = 0; !explicitlyValid && i < bindingValidationRules.size(); i++) {
BindingValidationRule bindingValidationRule = bindingValidationRules.get(i);
if (javaFileTypeName.matches(bindingValidationRule.getTypeRegex())) {
explicitlyValid = bindingValue != null && bindingValue.matches(bindingValidationRule.getValidBindingRegex());
}
}
}
if (!explicitlyValid) {
int lineNumber = getLineNumber();
if (isKeyPath()) {
boolean checkKeyPath = true;
String valueNamespace = getValueNamespace();
if (valueNamespace != null) {
// Support for the "var" value namespace
if ("var".equals(valueNamespace)) {
if (htmlCache != null && bindingValue != null) {
htmlCache.getVars().add(bindingValue);
}
checkKeyPath = false;
}
else {
checkKeyPath = false;
}
}
if (checkKeyPath) {
BindingValueKeyPath bindingValueKeyPath = new BindingValueKeyPath(bindingValue, javaFileType, javaProject, cache);
// NTS: Technically these need to be related to every java file name in the key path
if (!bindingValueKeyPath.isValid() || (bindingValueKeyPath.isWOComponent() && !PreferenceConstants.IGNORE.equals(missingComponentSeverity)) || (bindingValueKeyPath.isNSKeyValueCoding() && !PreferenceConstants.IGNORE.equals(missingNSKVCSeverity) && !bindingValueKeyPath.isNSCollection())) {
boolean warning;
if (bindingValueKeyPath.isValid()) {
warning = false;
}
else if (bindingValueKeyPath.isWOComponent()) {
warning = PreferenceConstants.WARNING.equals(missingComponentSeverity);
}
else if (bindingValueKeyPath.isNSKeyValueCoding()) {
warning = PreferenceConstants.WARNING.equals(missingCollectionSeverity);
}
else {
warning = false;
}
String invalidKey = bindingValueKeyPath.getInvalidKey();
String validKeyPath = bindingValueKeyPath.getValidKeyPath();
if ((validKeyPath == null || validKeyPath.length() == 0) && htmlCache != null && htmlCache.getVars().contains(invalidKey)) {
// the value specified is a component var
}
else if (validKeyPath != null) {
if (validKeyPath.length() == 0) {
problems.add(new WodBindingValueProblem(element, this, bindingName, "There is no key '" + invalidKey + "' in " + javaFileType.getElementName(), getValuePosition(), lineNumber, false));
}
else {
problems.add(new WodBindingValueProblem(element, this, bindingName, "There is no key '" + invalidKey + "' for the keypath '" + validKeyPath + "' in " + javaFileType.getElementName(), getValuePosition(), lineNumber, false));
}
}
}
else if (bindingValueKeyPath.isNSCollection()) {
if (!PreferenceConstants.IGNORE.equals(missingCollectionSeverity)) {
String validKeyPath = bindingValueKeyPath.getValidKeyPath();
if (validKeyPath != null) {
if (validKeyPath.length() == 0) {
problems.add(new WodBindingValueProblem(element, this, bindingName, "Unable to verify key '" + bindingValueKeyPath.getInvalidKey() + "' because " + javaFileType.getElementName() + " passes through a collection", getValuePosition(), lineNumber, PreferenceConstants.WARNING.equals(missingCollectionSeverity)));
}
else {
problems.add(new WodBindingValueProblem(element, this, bindingName, "Unable to verify key '" + bindingValueKeyPath.getInvalidKey() + "' because the keypath '" + validKeyPath + "' in " + javaFileType.getElementName() + " passes through a collection", getValuePosition(), lineNumber, PreferenceConstants.WARNING.equals(missingCollectionSeverity)));
}
}
}
}
else if (bindingValueKeyPath.isWOComponent()) {
if (!PreferenceConstants.IGNORE.equals(missingComponentSeverity)) {
String validKeyPath = bindingValueKeyPath.getValidKeyPath();
if (validKeyPath != null) {
if (validKeyPath.length() == 0) {
problems.add(new WodBindingValueProblem(element, this, bindingName, "Unable to verify the key '" + bindingValueKeyPath.getInvalidKey() + "' because " + javaFileType.getElementName() + " is a component.", getValuePosition(), lineNumber, PreferenceConstants.WARNING.equals(missingComponentSeverity)));
}
else {
problems.add(new WodBindingValueProblem(element, this, bindingName, "Unable to verify the key '" + bindingValueKeyPath.getInvalidKey() + "' because the keypath '" + validKeyPath + "' in " + javaFileType.getElementName() + " is a component.", getValuePosition(), lineNumber, PreferenceConstants.WARNING.equals(missingComponentSeverity)));
}
}
}
}
else if (bindingValueKeyPath.isNSKeyValueCoding()) {
if (!PreferenceConstants.IGNORE.equals(missingNSKVCSeverity)) {
String validKeyPath = bindingValueKeyPath.getValidKeyPath();
if (validKeyPath != null) {
if (validKeyPath.length() == 0) {
problems.add(new WodBindingValueProblem(element, this, bindingName, "Unable to verify the key '" + bindingValueKeyPath.getInvalidKey() + "' because " + javaFileType.getElementName() + " implements NSKeyValueCoding", getValuePosition(), lineNumber, PreferenceConstants.WARNING.equals(missingNSKVCSeverity)));
}
else {
problems.add(new WodBindingValueProblem(element, this, bindingName, "Unable to verify the key '" + bindingValueKeyPath.getInvalidKey() + "' because the keypath '" + validKeyPath + "' in " + javaFileType.getElementName() + " implements NSKeyValueCoding", getValuePosition(), lineNumber, PreferenceConstants.WARNING.equals(missingNSKVCSeverity)));
}
}
}
}
else if (!PreferenceConstants.IGNORE.equals(ambiguousSeverity) && bindingValueKeyPath.isAmbiguous()) {
String validKeyPath = bindingValueKeyPath.getValidKeyPath();
if (validKeyPath != null) {
if (validKeyPath.length() == 0) {
problems.add(new WodBindingValueProblem(element, this, bindingName, "Unable to verify the key '" + bindingValueKeyPath.getInvalidKey() + "' in " + javaFileType.getElementName(), getValuePosition(), lineNumber, PreferenceConstants.WARNING.equals(ambiguousSeverity)));
}
else {
problems.add(new WodBindingValueProblem(element, this, bindingName, "Unable to verify the key '" + bindingValueKeyPath.getInvalidKey() + "' for the path '" + validKeyPath + "' in " + javaFileType.getElementName(), getValuePosition(), lineNumber, PreferenceConstants.WARNING.equals(ambiguousSeverity)));
}
}
}
String operator = bindingValueKeyPath.getOperator();
if (!PreferenceConstants.IGNORE.equals(atOperatorSeverity) && operator != null && !BindingReflectionUtils.getArrayOperators().contains(operator)) {
problems.add(new WodBindingValueProblem(element, this, bindingName, "Unable to verify operator '" + operator + "'", getValuePosition(), lineNumber, PreferenceConstants.WARNING.equals(atOperatorSeverity)));
}
String helperFunction = bindingValueKeyPath.getHelperFunction();
if (!PreferenceConstants.IGNORE.equals(helperFunctionSeverity) && helperFunction != null) {
problems.add(new WodBindingValueProblem(element, this, bindingName, "Unable to verify helper function '" + helperFunction + "'", getValuePosition(), lineNumber, PreferenceConstants.WARNING.equals(helperFunctionSeverity)));
}
// else {
// String[] validApiValues = WodBindingUtils.getValidValues(elementType, getName(), typeToApiModelWoCache);
// if (validApiValues != null &&
// !Arrays.asList(validApiValues).contains(bindingValue))
// {
// problems.add(new WodProblem(wodModel, "The .api file for " + wodJavaType.getElementName() + " declares '" + bindingValue + "' to be an invalid value.", getValuePosition(), false));
// }
// }
if (apiBinding != null) {
if (apiBinding.isWillSet()) {
if (!bindingValueKeyPath.isSettable()) {
// problems.add(new WodBindingValueProblem(bindingName, "The key '" + getName() + "' must have a 'set' method.", getValuePosition(), lineNumber, false));
}
}
}
}
}
else if (apiBinding != null) {
if (apiBinding.isWillSet() && !isCaret()) {
// problems.add(new WodBindingValueProblem(bindingName, "The key '" + getName() + "' cannot be a constant value.", getValuePosition(), lineNumber, false));
}
}
String deprecationSeverity = Activator.getDefault().getPluginPreferences().getString(PreferenceConstants.DEPRECATED_BINDING_SEVERITY_KEY);
if (!PreferenceConstants.IGNORE.equals(deprecationSeverity)) {
BindingValueKeyPath bindingValueKeyPath = new BindingValueKeyPath(bindingValue, javaFileType, javaProject, cache);
if (bindingValueKeyPath.isValid() && bindingValueKeyPath.getBindingKeys() != null) {
for (BindingValueKey bindingKey : bindingValueKeyPath.getBindingKeys()) {
if (BindingReflectionUtils.bindingPointsToDeprecatedValue(bindingKey)) {
problems.add(new WodBindingDeprecationProblem(element, this, bindingName, "The key '" +bindingName + "' uses a value that is deprecated.", getValuePosition(), lineNumber, PreferenceConstants.WARNING.equals(deprecationSeverity)));
break;
}
}
}
}
String invalidOGNLSeverity = Activator.getDefault().getPluginPreferences().getString(PreferenceConstants.INVALID_OGNL_SEVERITY_KEY);
if (!PreferenceConstants.IGNORE.equals(invalidOGNLSeverity) && isOGNL()) {
boolean inQuotes = bindingValue.startsWith("\"");
if (inQuotes) {
bindingValue = bindingValue.substring(1, bindingValue.length() - 1);
}
String ognl = bindingValue.substring(1);
ognl = ognl.replaceAll("\\\\'", " ");
ognl = ognl.replaceAll("'[^']*'", "''");
if (inQuotes) {
ognl = ognl.replaceAll("\\\\\"[^\"]*\\\\\"", "\\\"\\\"");
}
else {
ognl = ognl.replaceAll("\\\\\"", " ");
ognl = ognl.replaceAll("\"[^\"]*\"", "\"\"");
}
ognl = ognl + " ";
int identifierStartChar = -1;
for (int i = 0; i < ognl.length(); i++) {
char ch = ognl.charAt(i);
if (identifierStartChar == -1) {
if (Character.isJavaIdentifierStart(ch) || ch == '@' || ch == '#') {
identifierStartChar = i;
}
}
else if (!Character.isJavaIdentifierPart(ch) && ch != '.' && ch != '@' && ch != '#') {
String ognlBindingValue = ognl.substring(identifierStartChar, i);
// null
if (!ognlBindingValue.startsWith("@") && !ognlBindingValue.startsWith("#") && !AbstractWodBinding.VALID_OGNL_VALUES.contains(ognlBindingValue.toLowerCase())) {
// function call
String nextStr = ognl.substring(i).trim();
if (!nextStr.startsWith("(")) {
SimpleWodBinding ognlBinding = new SimpleWodBinding(bindingNamespace, bindingName, "ognl", ognlBindingValue, getNamespacePosition(), getNamePosition(), getValueNamespacePosition(), getValuePosition(), getLineNumber());
try {
List<WodProblem> ognlProblems = new LinkedList<WodProblem>();
ognlBinding.fillInBindingProblems(element, apiBinding, javaProject, javaFileType, ognlProblems, cache, htmlCache);
for (WodProblem ognlProblem : ognlProblems) {
problems.add(new WodBindingValueProblem(element, this, bindingName, ognlProblem.getMessage(), getValuePosition(), lineNumber, ognlProblem.isWarning() || PreferenceConstants.WARNING.equals(invalidOGNLSeverity)));
}
}
catch (Exception e) {
Activator.getDefault().log(e);
}
}
}
identifierStartChar = -1;
}
}
}
}
}
}
// IApiBinding stub impl
public String getDefaults() {
return null;
}
public int getSelectedDefaults() {
return ApiUtils.getSelectedDefaults(this);
}
public String[] getValidValues(String partialValue, IJavaProject javaProject, IType componentType, TypeCache typeCache) {
return new String[0];
}
public boolean isRequired() {
return false;
}
public boolean isWillSet() {
return false;
}
public boolean isValueWithin(IRegion region) {
Position valuePosition = getValuePosition();
return valuePosition != null && valuePosition.getOffset() <= region.getOffset() && valuePosition.getOffset() + valuePosition.getLength() > region.getOffset();
}
public boolean isNameWithin(IRegion region) {
Position namePosition = getNamePosition();
return namePosition != null && namePosition.getOffset() <= region.getOffset() && namePosition.getOffset() + namePosition.getLength() > region.getOffset();
}
@Override
public String toString() {
return "[" + getClass().getName() + ": name = " + getName() + "; value = " + getValue() + "]";
}
public List<WodProblem> getBindingProblems(String elementType, IType javaFileType, TypeCache typeCache, HtmlElementCache htmlCache) throws JavaModelException, ApiModelException {
List<WodProblem> problems = new LinkedList<WodProblem>();
IApiBinding apiBinding = null;
IWodElement element = null;
if (elementType != null) {
element = new SimpleWodElement("_temp", elementType);
Wo wo = element.getApi(javaFileType.getJavaProject(), typeCache);
if (wo != null) {
apiBinding = wo.getBinding(getName());
}
}
fillInBindingProblems(element, apiBinding, javaFileType.getJavaProject(), javaFileType, problems, new TypeCache(), htmlCache);
return problems;
}
}