Package de.hybris.yfaces.component

Source Code of de.hybris.yfaces.component.YComponentInfo

/*
* Copyright 2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package de.hybris.yfaces.component;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import de.hybris.yfaces.YFacesException;

/**
* Holds {@link YComponent} specific meta information.
*
* @author Denny.Strietzbaum
*/
public class YComponentInfo {
  private static final Logger log = Logger.getLogger(YComponentInfo.class);

  // various placeholders for error messages
  // placeholder: specification class
  private static final String PLACEHOLDER_SPECCLASS = "specClass";
  // placeholder: imlementation
  private static final String PLACEHOLDER_IMPLCLASS = "implClass";
  // placeholder: reserved properties (var, id, etc)
  private static final String PLACEHOLDER_PROPERTIES = "properties";

  /**
   * Enumeration of possible error state. Supports placeholders whose values are extracted from a
   * {@link YComponentInfo}
   */
  public static enum ERROR_STATE {

    /** When no component interface (specification) is provided */
    SPEC_IS_MISSING("No specification specified"),

    /** When specification is not an interface */
    SPEC_IS_NO_INTERFACE("Invalid specification {" + PLACEHOLDER_SPECCLASS + "} - no interface"),

    /** When specification is not an YComponent */
    SPEC_IS_NO_YCMP("Invalid specification {" + PLACEHOLDER_SPECCLASS + "} - no "
        + YComponent.class.getSimpleName()),

    /** When specification is not loadable (classnotfound etc.) */
    SPEC_NOT_LOADABLE("Can't load specification class {" + PLACEHOLDER_SPECCLASS + "}"),

    /** When no component class (implementation) is provided */
    IMPL_IS_MISSING("No implementation specified"),

    /** Implementation is no implementation (but interface) */
    IMPL_IS_INTERFACE("Invalid implementation; got interface but no class"),

    /** When implementation is not an YComponent */
    IMPL_IS_NO_YCMP("Invalid implementation {" + PLACEHOLDER_IMPLCLASS
        + "}; not an instanceof " + YComponent.class.getSimpleName()),

    /** When implementation doesn't match specification */
    IMPL_UNASSIGNABLE_TO_SPEC("Invalid implementation {" + PLACEHOLDER_IMPLCLASS
        + "}; not an instance of {" + PLACEHOLDER_SPECCLASS + "}"),

    /** When implementation is not loadable (classnotfound etc.) */
    IMPL_NOT_LOADABLE("Can't load implementation class {" + PLACEHOLDER_IMPLCLASS + "}"),

    /** When no component id attribute is specified */
    VIEW_ID_NOT_SPECIFIED("No ID specified"),

    /** When no component var attribute is specified */
    VIEW_VAR_NOT_SPECIFIED("No VAR specified"),

    /** When a reserved attribute is marked as being injectable */
    VIEW_INJECTABLE_PROP_FORBIDDEN("Found reserved property; one of {" + PLACEHOLDER_PROPERTIES
        + "}"), ;

    // Pattern for detecting placeholders
    private static final Pattern placeHolderPattern = Pattern.compile("\\{(.*?)\\}");

    public static String getFormattedErrorMessage(Collection<ERROR_STATE> errors,
        YComponentInfo cmpInfo, Class<?> customImplClass) {
      String result = null;
      if (!errors.isEmpty()) {
        result = cmpInfo.getId() != null ? cmpInfo.getId() : "";
        for (ERROR_STATE error : errors) {
          result = result + "," + error.getFormattedErrorMessage(cmpInfo);
        }
      }
      return result;

    }

    private String msg = null;

    private ERROR_STATE(String msg) {
      this.msg = msg;
    }

    /**
     * Returns a formatted error message based on the passed {@link YComponentInfo}
     *
     * @param cmpInfo
     *            {@link YComponentInfo}
     * @return String
     */
    public String getFormattedErrorMessage(YComponentInfo cmpInfo) {
      return this.getFormattedErrorMessage(cmpInfo, null);
    }

    /**
     * Returns a formatted error message based on the passed {@link YComponentInfo}. An optional
     * custom implementation is used instead of
     * {@link YComponentInfo#getImplementationClassName()}
     *
     * @param cmpInfo
     *            {@link YComponentInfo}
     * @param customImplClass
     *            Class
     * @return String
     */
    public String getFormattedErrorMessage(YComponentInfo cmpInfo, Class<?> customImplClass) {
      String result = "";
      String msg = this.msg;

      Matcher parameter = placeHolderPattern.matcher(msg);
      int start = 0;
      while (parameter.find()) {
        String param = parameter.group(1);
        if (param.equals(PLACEHOLDER_SPECCLASS)) {
          param = cmpInfo.getSpecificationClassName();
        } else if (param.equals(PLACEHOLDER_IMPLCLASS)) {
          param = (customImplClass == null) ? cmpInfo.getImplementationClassName()
              : customImplClass.getName();
        } else if (param.equals(PLACEHOLDER_PROPERTIES)) {
          param = Arrays.asList(RESERVED_PROPERTY_NAMES).toString();
        }

        result = result + msg.substring(start, parameter.start()) + param;
        start = parameter.end();
      }

      result = result + msg.substring(start);

      return result;
    }

  }

  // reserved (forbidden) attributes;
  // their usage is not allowed as injectable component parameter
  private static final String[] RESERVED_PROPERTY_NAMES = new String[] { "facet" };

  // Class to Methods lookup map.
  private static Map<String, Map<String, Method>> classToPropertiesMap = new HashMap<String, Map<String, Method>>();

  //
  // Members
  //

  // raw (unevaluated) attribute values
  private String id = null;
  private String cmpVar = null;
  private String specClassName = null;
  private String implClassName = null;

  // Properties which gets evaluated and injected; specified by renderer
  private Set<String> injectableAttributes = Collections.emptySet();

  // Properties which can be injected; specified by component class
  private Map<String, Method> writableProperties = null;

  // instance of specification class
  private Class<?> specClass = null;

  // instance of implementation class
  private Class<?> implClass = null;

  private String cmpName = null;

  private String namespace = null;
  private URL url = null;

  // all errors which are detected after a verification
  private Set<ERROR_STATE> errors = null;

  protected YComponentInfo() {
    this.injectableAttributes = Collections.emptySet();
  }

  /**
   * Constructor. Initializes this instance by parsing the url stream.
   */
  public YComponentInfo(String id, String varName, String specClassname, String implClassName) {
    this.id = id;
    this.cmpVar = varName;
    this.specClassName = specClassname;
    this.implClassName = implClassName;
    this.injectableAttributes = Collections.emptySet();
  }

  public YComponentInfo(String namespace, URL url) {
    this.namespace = namespace;
    this.url = url;
  }

  /**
   * Verifies the component.<br/>
   * Checks for specification, implementation, non-duplicate 'id' and 'var' values.<br/>
   *
   */
  public Set<ERROR_STATE> verifyComponent() {
    if (this.errors == null) {
      this.errors = EnumSet.noneOf(ERROR_STATE.class);
      assertImplementationClassName();
      assertSpecificationClassName();
      assertIdAndVarName();

      this.assertSpecificationClass();
      this.assertImplementationClass();

      this.assertProperties();

      if (this.implClass != null) {
        this.refreshWritableProperties();
      }
    }
    return Collections.unmodifiableSet(this.errors);
  }

  /**
   * Returns the classname of the interface/ specification class.
   *
   * @return classname
   */
  public String getSpecificationClassName() {
    return this.specClassName;
  }

  /**
   * Sets the classname of the interface/specification class.<br/>
   * Nullifies an already (optional) created instance of the implementation class.<br/>
   * Does no verification at all.<br/>
   *
   * @param className
   *            classname
   */
  protected void setSpecificationClassName(String className) {
    String newClass = (className == null || className.trim().length() == 0) ? null : className;
    if (newClass != null && !newClass.equals(this.specClassName)) {
      this.specClassName = newClass;
      this.implClass = null;
    }
  }

  /**
   * Returns the classname of the implementation class.
   *
   * @return classname
   */
  public String getImplementationClassName() {
    return this.implClassName;
  }

  /**
   * Sets the classname of the implementation class.
   *
   * @param className
   */
  protected void setImplementationClassName(String className) {
    String newClass = (className == null || className.trim().length() == 0) ? null : className;
    if (newClass != null && !newClass.equals(this.implClassName)) {
      this.implClassName = newClass;
      this.implClass = null;
    }
  }

  public String getId() {
    return this.id;
  }

  protected void setId(String id) {
    this.id = id;
  }

  public String getVarName() {
    return this.cmpVar;
  }

  protected void setVarName(String varName) {
    this.cmpVar = varName;
  }

  public Class<?> getSpecificationClass() {
    return this.specClass;
  }

  public Class<?> getImplementationClass() {
    return this.implClass;
  }

  public Map<String, Method> getAllComponentProperties() {
    return this.writableProperties;
  }

  public Collection<String> getInjectableProperties() {
    return this.injectableAttributes;
  }

  protected void addInjectableProperty(String property) {
    if (this.injectableAttributes == Collections.EMPTY_SET) {
      this.injectableAttributes = new HashSet<String>();
    }
    this.injectableAttributes.add(property);
  }

  protected void addInjectableProperties(String... properties) {
    for (String property : properties) {
      this.addInjectableProperty(property);
    }
  }

  /**
   * Creates and returns an {@link YComponent} instance based on this information.
   *
   * @return {@link YComponent}
   */
  public <T extends YComponent> T createDefaultComponent() {
    T result = null;
    try {
      result = (T) this.getImplementationClass().newInstance();
      ((AbstractYComponent) result).setId(this.id);
    } catch (Exception e) {
      throw new YFacesException("Can't create " + YComponent.class.getName() + " instance ("
          + implClass + ")", e);
    }
    return result;
  }

  private void refreshWritableProperties() {
    // refresh injectable properties
    this.writableProperties = classToPropertiesMap.get(this.implClassName);
    if (this.writableProperties == null) {
      this.writableProperties = this.findWriteableProperties(implClass,
          AbstractYComponent.class);
      classToPropertiesMap.put(this.implClassName, this.writableProperties);
    }
  }

  private Map<String, Method> findWriteableProperties(Class<?> startClass, Class<?> endClass) {

    Map<String, Method> result = new HashMap<String, Method>();
    try {
      // find setter for attributes
      PropertyDescriptor[] descriptors = Introspector.getBeanInfo(startClass,
          AbstractYComponent.class).getPropertyDescriptors();

      for (PropertyDescriptor descriptor : descriptors) {
        String name = descriptor.getName();
        Method writeMethod = descriptor.getWriteMethod();

        if (writeMethod != null) {
          result.put(name, writeMethod);
        }

        if (log.isDebugEnabled()) {
          if (writeMethod != null) {
            log.debug(this.id + " add property " + descriptor.getName() + " ("
                + descriptor.getWriteMethod() + ")");
          } else {
            log.debug(this.id + " skip property " + name + " (read only)");
          }
        }
      }
    } catch (IntrospectionException e) {
      e.printStackTrace();
    }
    return result;
  }

  private boolean assertSpecificationClassName() {
    if (specClassName == null || specClassName.trim().length() == 0) {
      this.errors.add(ERROR_STATE.SPEC_IS_MISSING);
    }
    return this.errors.isEmpty();
  }

  /**
   * Asserts whether an implementation classname is available.<br/>
   * Does not assert whether class can be loaded.
   *
   * @return true when assertion succeeds
   */
  private boolean assertImplementationClassName() {
    if (implClassName == null || implClassName.trim().length() == 0) {
      this.errors.add(ERROR_STATE.IMPL_IS_MISSING);
    }
    return this.errors.isEmpty();
  }

  /**
   * Asserts whether an ID and VAR value is available.
   *
   * @return true when assertion succeeds
   */
  private boolean assertIdAndVarName() {
    if (id == null || id.trim().length() == 0) {
      this.errors.add(ERROR_STATE.VIEW_ID_NOT_SPECIFIED);
    }

    if (cmpVar == null || cmpVar.trim().length() == 0) {
      this.errors.add(ERROR_STATE.VIEW_VAR_NOT_SPECIFIED);
    }

    return this.errors.isEmpty();
  }

  private boolean assertSpecificationClass() {
    if (this.specClass == null && !this.errors.contains(ERROR_STATE.SPEC_IS_MISSING)) {
      this.specClass = getClass(this.specClassName, ERROR_STATE.SPEC_NOT_LOADABLE);
    }

    if (specClass != null) {
      if (!specClass.isInterface()) {
        this.errors.add(ERROR_STATE.SPEC_IS_NO_INTERFACE);
      }

      if (!YComponent.class.isAssignableFrom(specClass)) {
        this.errors.add(ERROR_STATE.SPEC_IS_NO_YCMP);
      }
    }
    return this.errors.isEmpty();
  }

  private void assertImplementationClass() {
    if (this.implClass == null && !this.errors.contains(ERROR_STATE.IMPL_IS_MISSING)) {
      this.implClass = getClass(this.implClassName, ERROR_STATE.IMPL_NOT_LOADABLE);
    }

    if (implClass != null) {
      final Set<ERROR_STATE> _implErrors = this.assertCustomImplementationClass(implClass);

      if (!_implErrors.isEmpty()) {
        this.implClass = null;
        this.errors.addAll(_implErrors);
      }
    }
  }

  /**
   * Asserts the passed class whether it can be used with this component configuration.<br/>
   *
   * Possible verification errors are:<br/> {@link ERROR_STATE#IMPL_IS_INTERFACE}<br/>
   * {@link ERROR_STATE#IMPL_IS_NO_YCMP}<br/> {@link ERROR_STATE#IMPL_UNASSIGNABLE_TO_SPEC}<br/>
   *
   * @param implClass
   * @return result
   */
  public Set<ERROR_STATE> assertCustomImplementationClass(Class<?> implClass) {
    Set<ERROR_STATE> result = EnumSet.noneOf(ERROR_STATE.class);

    if (implClass.isInterface()) {
      result.add(ERROR_STATE.IMPL_IS_INTERFACE);
    }

    if (!YComponent.class.isAssignableFrom(implClass)) {
      result.add(ERROR_STATE.IMPL_IS_NO_YCMP);
    }

    if (specClass != null && !specClass.isAssignableFrom(implClass)) {
      result.add(ERROR_STATE.IMPL_UNASSIGNABLE_TO_SPEC);
    }
    return result;

  }

  private boolean assertProperties() {
    for (String s : RESERVED_PROPERTY_NAMES) {
      // if (this.writableProperties.containsKey(s))
      // this.errors.add(ERROR_STATE.FORBIDDEN_PROPERTY);

      if (this.injectableAttributes.contains(s)) {
        this.errors.add(ERROR_STATE.VIEW_INJECTABLE_PROP_FORBIDDEN);
      }
    }
    return this.errors.isEmpty();
  }

  private <T> Class<T> getClass(String classname, ERROR_STATE catchError) {
    Class<T> result = null;
    try {
      result = (Class<T>) Class.forName(classname);
      this.errors.remove(catchError);
    } catch (final Exception e) {
      if (catchError != null) {
        this.errors.add(catchError);
      } else {
        throw new YFacesException("Can't load class " + classname, e);
      }
    }
    return result;
  }

  protected void setComponentName(final String name) {
    this.cmpName = name;
  }

  public String getComponentName() {
    return cmpName;
  }

  @Override
  public String toString() {
    //    final String result = "id:" + this.id + "; spec:" + this.specClassName + "; impl:"
    //        + this.implClassName + "; var:" + this.cmpVar + "; injects:"
    //        + this.injectableAttributes;
    String result = this.url.toExternalForm();
    return result;
  }

  public URL getURL() {
    return url;
  }

  protected void setURL(URL url) {
    this.url = url;
  }

  public String getNamespace() {
    return this.namespace;
  }

  protected void setNamespace(String ns) {
    this.namespace = ns;
  }

  @Override
  public boolean equals(Object obj) {
    return this.url.toExternalForm().equals(((YComponentInfo) obj).url.toExternalForm());
  }

  @Override
  public int hashCode() {
    return this.url.toExternalForm().hashCode();
  }

}
TOP

Related Classes of de.hybris.yfaces.component.YComponentInfo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.