Java tutorial
/******************************************************************************* * Copyright (c) 2004, 2010 BREDEX GmbH. * 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: * BREDEX GmbH - initial API and implementation and/or initial documentation *******************************************************************************/ package org.eclipse.jubula.rc.common.components; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.eclipse.jubula.rc.common.AUTServerConfiguration; import org.eclipse.jubula.rc.common.Constants; import org.eclipse.jubula.rc.common.logger.AutServerLogger; import org.eclipse.jubula.tools.objects.IComponentIdentifier; import org.eclipse.jubula.tools.xml.businessmodell.Profile; /** * @created Nov 30, 2006 */ public abstract class FindComponentBP { /** the logger */ private static final AutServerLogger LOG = new AutServerLogger(FindComponentBP.class); /** the factor how strong path equivalenz will increase total equivalenz */ private double m_pathFactor = Constants.PATH_FACTOR; /** the factor how strong context equivalenz will increase total equivalenz */ private double m_contextFactor = Constants.CONTEXT_FACTOR; /** the threshold value, when a component will be selected as equivalents */ private double m_thresholdValue = Constants.THRESHOLD_VALUE; /** the factor how strong name equivalenz will increase total equivalenz */ private double m_nameFactor = Constants.NAME_FACTOR; /** the current aut hierarchy */ private AUTHierarchy m_hierarchy; /** * Searchs for the component in the AUT with the given * <code>componentIdentifier</code>. * * @param componentIdentifier the identifier created in object mapping mode * @param hierarchy the current aut hierarchy * @throws IllegalArgumentException if the given identifer is null or <br> * the hierarchy is not valid: empty or containing null elements * @return the instance of the component of the AUT */ protected Object findComponent(final IComponentIdentifier componentIdentifier, final AUTHierarchy hierarchy) throws IllegalArgumentException { m_hierarchy = hierarchy; setProfile(AUTServerConfiguration.getInstance().getProfile()); // Fuzzy logic parameter List hierarchyNames = null; // parameter check Validate.notNull(componentIdentifier, "The component identifier must not be null."); //$NON-NLS-1$ hierarchyNames = componentIdentifier.getHierarchyNames(); Validate.noNullElements(hierarchyNames, "The component identifier contains no hierarchy information."); //$NON-NLS-1$ Iterator allComponents = new ArrayList(m_hierarchy.getHierarchyMap().values()).iterator(); HierarchyContainer bestMatch = null; double bestMatchPercentage = 0; double equivalence = 0; String suppClassName = componentIdentifier.getSupportedClassName(); final AUTServerConfiguration autServerConf = AUTServerConfiguration.getInstance(); int numberOfOtherMatchingComponents = 0; while (allComponents.hasNext()) { HierarchyContainer current = (HierarchyContainer) allComponents.next(); Object currComp = current.getCompID().getComp(); // check class compatibility first if (isAvailable(currComp) && isSupportedComponent(currComp) && ((suppClassName != null && (checkTestableClass(autServerConf, suppClassName, currComp))) || componentIdentifier.getComponentName().equals(getCompName(currComp)))) { equivalence = computeEquivalence(componentIdentifier, current); if (meetsThreshold(equivalence)) { numberOfOtherMatchingComponents++; } if (equivalence > bestMatchPercentage) { bestMatch = current; bestMatchPercentage = equivalence; } } } Object technicalComponent = null; if (bestMatch != null && meetsThreshold(bestMatchPercentage)) { technicalComponent = bestMatch.getCompID().getComp(); } componentIdentifier.setMatchPercentage(bestMatchPercentage); componentIdentifier.setNumberOfOtherMatchingComponents(numberOfOtherMatchingComponents); return technicalComponent; } /** * @param currComp the current component * @return false if component is available. */ protected abstract boolean isAvailable(Object currComp); /** * Checks if the given currcomp class or one of its superclass is testable * with the given suppClassName. * @param autServerConf the AUTServerConfiguration * @param suppClassName the class name of the supported component * @param currComp the current component to test * @return true if testable, false otherwise. */ protected boolean checkTestableClass(final AUTServerConfiguration autServerConf, String suppClassName, Object currComp) { boolean isTestable = false; try { isTestable = suppClassName.equals(autServerConf.getTestableClass(currComp.getClass()).getName()); } catch (IllegalArgumentException iae) { // NOPMD by zeb on 10.04.07 15:13 // The class is not supported // Do nothing -> isTestable is still false } Class superClass = currComp.getClass(); while (!isTestable && superClass != null) { superClass = superClass.getSuperclass(); try { isTestable = suppClassName.equals(autServerConf.getTestableClass(superClass).getName()); } catch (IllegalArgumentException iae) { // NOPMD by zeb on 10.04.07 15:13 // The class is not supported // Do nothing -> isTestable is still false } } return isTestable; } /** * checks if a component is supported * @param component Component * @return boolean */ protected boolean isSupportedComponent(Object component) { try { AUTServerConfiguration.getInstance().getTestableClass(component.getClass()); } catch (IllegalArgumentException e) { return false; } return true; } /** * @param profile the profile to set */ private void setProfile(Profile profile) { if (profile != null) { m_nameFactor = profile.getNameFactor(); m_pathFactor = profile.getPathFactor(); m_contextFactor = profile.getContextFactor(); m_thresholdValue = profile.getThreshold(); } } /** * computes the Equivalence of 2 components * * @param compIdent * ComponentIdentifier * @param current * HierarchyContainer * @return double indicating the equivalence; THIS METHOD uses a shortcut to * decide continuuing the equivalence computing or not - this may * lead to a "guessed" equivalence which is higher as the real * equivalence but still lower than the specified threshold * equivalence. */ private double computeEquivalence(IComponentIdentifier compIdent, HierarchyContainer current) { double equivalence = 0; // name equivalence String name1 = compIdent.getComponentName(); String name2 = current.getName(); double nameEquivalence = 0; if (isGeneratedName(compIdent.getComponentClassName(), compIdent.getComponentName())) { nameEquivalence = getNameEquivalence(name1, name2); } else { nameEquivalence = name1.equals(name2) ? 1 : 0; } /* check for computing shortcut if equivalence may not meet the * threshold, although path==1 && context==1 */ equivalence = equivalence(nameEquivalence, 1, 1); if (!meetsThreshold(equivalence)) { return equivalence; } // path equivalence double pathEquivalence = getPathEquivalence(compIdent, current); /* check for computing shortcut if equivalence may not meet the * threshold, although context==1 */ equivalence = equivalence(nameEquivalence, pathEquivalence, 1); if (!meetsThreshold(equivalence)) { return equivalence; } // context equivalence double contextEquivalence = getContextEquivalence(compIdent, current); // calculate total equivalence equivalence = equivalence(nameEquivalence, pathEquivalence, contextEquivalence); logEquivalence(compIdent, current, equivalence, nameEquivalence, pathEquivalence, contextEquivalence); return equivalence; } /** * @param equivalence the current equivalence * @return true if it meet's the threshold, false otherwise */ private boolean meetsThreshold(double equivalence) { return (equivalence - m_thresholdValue + 1e-2) > 0; } /** * @param nameE * the current name equivalence * @param pathE * the current path equivalence * @param contE * the current context equivalence * @return the weighted equivalence */ private double equivalence(double nameE, double pathE, double contE) { return m_nameFactor * nameE + m_pathFactor * pathE + m_contextFactor * contE; } /** * log the results from the equivalence computation * @param componentIdentifier which component is looked up * @param current which GUI component is evaluated * @param equivalence computed equivalence * @param nameEquivalence equivalence for name * @param pathEquivalence equivalence for path * @param contextEquivalence equivalence for context */ private void logEquivalence(IComponentIdentifier componentIdentifier, HierarchyContainer current, double equivalence, double nameEquivalence, double pathEquivalence, double contextEquivalence) { if (LOG.isInfoEnabled()) { StringBuffer txt = new StringBuffer(500); txt.append("Equivalence values for Identifier "); //$NON-NLS-1$ txt.append(componentIdentifier.getComponentNameToDisplay()); txt.append(" , matched against "); //$NON-NLS-1$ txt.append(current.getName()); txt.append(" , threshold value: "); //$NON-NLS-1$ txt.append(m_thresholdValue); txt.append("\n"); //$NON-NLS-1$ txt.append("Equivalence total: "); //$NON-NLS-1$ txt.append(equivalence); txt.append(" name: "); //$NON-NLS-1$ txt.append(nameEquivalence * m_nameFactor); txt.append(" path: "); //$NON-NLS-1$ txt.append(pathEquivalence * m_pathFactor); txt.append(" context: "); //$NON-NLS-1$ txt.append(contextEquivalence * m_contextFactor); final String txtString = txt.toString(); LOG.info(txtString); } } /** * computes the Equivalence of 2 names<p> * Example :<p> * jButton1 <=> jButton1 = 100.0<p> * jButton1 <=> jButton2 = 87.5<p> * jButton1 <=> jTextField1 = 20.0 <p> * jButton1 <=> jTextField2 = 10.0 <p> * @param name1 String * @param name2 String * @return percentage as double */ private double getNameEquivalence(String name1, String name2) { int diff = StringUtils.getLevenshteinDistance(name1, name2); double nameEquivalence = 1.0 / Math.max(name1.length(), name2.length()) * (Math.max(name1.length(), name2.length()) - diff); return nameEquivalence; } /** * @param comp ComponentIdentifier * @param hierarchyContainer SwtHierarchyContainer * @return percentage as double */ private double getPathEquivalence(IComponentIdentifier comp, HierarchyContainer hierarchyContainer) { if ((comp.getHierarchyNames() == null) || (comp.getHierarchyNames().size() == 0)) { return 0; } List l1 = comp.getHierarchyNames().subList(0, comp.getHierarchyNames().size() - 1); List l2 = new ArrayList(); HierarchyContainer iter = hierarchyContainer.getPrnt(); while (iter != null) { l2.add(0, iter.getName()); iter = iter.getPrnt(); } double pathEquivalence = 0; if (l1.size() == 0 && l2.size() == 0) { pathEquivalence = 1; } else { int diff = getLevenshteinListDistanceImp(l1, l2); pathEquivalence = 1.0 / Math.max(l1.size(), l2.size()) * (Math.max(l1.size(), l2.size()) - diff); } return pathEquivalence; } /** * @param comp ComponentIdentifier * @param hierarchyContainer HierarchyContainer * @return percentage as double */ private double getContextEquivalence(IComponentIdentifier comp, HierarchyContainer hierarchyContainer) { List compNeighbours = comp.getNeighbours(); List compContext = m_hierarchy.getComponentContext(hierarchyContainer.getCompID().getComp()); Collections.sort(compNeighbours); Collections.sort(compContext); double contextEquivalence = 0; final int compNeighboursSize = compNeighbours.size(); final int compContextSize = compContext.size(); if (compNeighboursSize == 0 && compContextSize == 0) { contextEquivalence = 1; } else { int diff = getLevenshteinListDistanceImp(compNeighbours, compContext); contextEquivalence = 1.0 / Math.max(compNeighboursSize, compContextSize) * (Math.max(compNeighboursSize, compContextSize) - diff); } return contextEquivalence; } /** * Find the Levenshtein distance between two Lists. Heavily based on the * {@link StringUtils#getLevenshteinDistance(String, String) Apache Commons * implementation} for Strings. * * @see StringUtils#getLevenshteinDistance(String, String) * @param s The first List. Must not be <code>null</code>. * @param t The second List. Must not be <code>null</code>. * @return result distance */ private int getLevenshteinListDistanceImp(List s, List t) { if (s == null || t == null) { throw new IllegalArgumentException("Lists must not be null"); //$NON-NLS-1$ } /* The difference between this impl. and the previous is that, rather than creating and retaining a matrix of size s.length()+1 by t.length()+1, //$NON-NLS-1$ we maintain two single-dimensional arrays of length s.length()+1. The first, d, //$NON-NLS-1$ is the 'current working' distance array that maintains the newest distance cost //$NON-NLS-1$ counts as we iterate through the characters of String s. Each time we increment //$NON-NLS-1$ the index of String t we are comparing, d is copied to p, the second int[]. Doing so //$NON-NLS-1$ allows us to retain the previous cost counts as required by the algorithm (taking //$NON-NLS-1$ the minimum of the cost count to the left, up one, and diagonally up and to the left //$NON-NLS-1$ of the current cost count being calculated). (Note that the arrays aren't really //$NON-NLS-1$ copied anymore, just switched...this is clearly much better than cloning an array //$NON-NLS-1$ or doing a System.arraycopy() each time through the outer loop.) //$NON-NLS-1$ Effectively, the difference between the two implementations is this one does not //$NON-NLS-1$ cause an out of memory condition when calculating the LD over two very large strings. //$NON-NLS-1$ */ final int sSize = s.size(); final int tSize = t.size(); if (sSize == 0) { return tSize; } else if (tSize == 0) { return sSize; } int p[] = new int[sSize + 1]; //'previous' cost array, horizontally int d[] = new int[sSize + 1]; // cost array, horizontally int swapPAndD[]; //placeholder to assist in swapping p and d // indexes into strings s and t int sIdx; // iterates through s int tIdx; // iterates through t Object currCharOfT; int cost; for (sIdx = 0; sIdx <= sSize; sIdx++) { p[sIdx] = sIdx; } for (tIdx = 1; tIdx <= tSize; tIdx++) { currCharOfT = t.get(tIdx - 1); d[0] = tIdx; for (sIdx = 1; sIdx <= sSize; sIdx++) { cost = s.get(sIdx - 1).equals(currCharOfT) ? 0 : 1; // minimum of cell to the left+1, to the top+1, diagonally left and up +cost d[sIdx] = Math.min(Math.min(d[sIdx - 1] + 1, p[sIdx] + 1), p[sIdx - 1] + cost); } // copy current distance counts to 'previous row' distance counts swapPAndD = p; p = d; d = swapPAndD; } // our last action in the above loop was to switch d and p, so p now // actually has the most recent cost counts return p[sSize]; } /** * checks if name is a genereated Name * @param className String * @param name String * @return boolean */ private boolean isGeneratedName(String className, String name) { return (name.indexOf(className) != -1); } /** * @param currentComponent the component to get the name for * @return the comp name of the current component */ protected abstract String getCompName(Object currentComponent); }