TPHExtractor verbessert, Transivitat-Algortmus ergaenzt und JUnit Tests dazu geschrieben. FacultyTest funktioniert

This commit is contained in:
Fayez Abu Alia 2018-11-15 20:52:27 +01:00
parent d4b6073760
commit a269ee9690
15 changed files with 424 additions and 85 deletions

View File

@ -38,11 +38,19 @@
<artifactId>reflections</artifactId> <artifactId>reflections</artifactId>
<version>0.9.11</version> <version>0.9.11</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.ow2.asm/asm -->
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>7.0</version>
</dependency>
<!--
<dependency> <dependency>
<groupId>org.ow2.asm</groupId> <groupId>org.ow2.asm</groupId>
<artifactId>asm-all</artifactId> <artifactId>asm-all</artifactId>
<version>[4.0.0,)</version> <version>[4.0.0,)</version>
</dependency> </dependency>
-->
<!-- <!--
<dependency> <dependency>
<groupId>org.bitbucket.mstrobel</groupId> <groupId>org.bitbucket.mstrobel</groupId>

View File

@ -125,6 +125,7 @@ public class BytecodeGen implements ASTVisitor {
for(ResultSet rs : listOfResultSets) { for(ResultSet rs : listOfResultSets) {
superClass = classOrInterface.getSuperClass().acceptTV(new TypeToDescriptor()); superClass = classOrInterface.getSuperClass().acceptTV(new TypeToDescriptor());
resultSet = rs; resultSet = rs;
tphExtractor.setResultSet(resultSet);
// Nur einmal ausführen!! // Nur einmal ausführen!!
if(!isVisited) { if(!isVisited) {
classOrInterface.accept(tphExtractor); classOrInterface.accept(tphExtractor);
@ -558,52 +559,5 @@ public class BytecodeGen implements ASTVisitor {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public class TPHExtractor extends AbstractASTWalker{
// Alle TPHs der Felder werden iKopf der Klasse definiert
// alle TPHs der Klasse: (TPH, is in Method?)
final HashMap<TypePlaceholder,Boolean> allTPHS = new HashMap<>();
MethodAndTPH methodAndTph;
Boolean inMethod = false;
public final ArrayList<MethodAndTPH> ListOfMethodsAndTph = new ArrayList<>();
final ArrayList<GenericInsertPair> allPairs = new ArrayList<>();
public final ArrayList<TPHConstraint> allCons = new ArrayList<>();
@Override
public void visit(TypePlaceholder tph) {
if(resultSet.resolveType(tph).resolvedType instanceof TypePlaceholder) {
TypePlaceholder resolvedTPH = (TypePlaceholder) resultSet.resolveType(tph).resolvedType;
if(inMethod)
methodAndTph.getTphs().add(resolvedTPH);
allTPHS.put(resolvedTPH,inMethod);
resultSet.resolveType(tph).additionalGenerics.forEach(ag ->{
if(ag.contains(resolvedTPH)&&ag.TA1.equals(resolvedTPH)&&!contains(allPairs,ag)) {
if(inMethod)
methodAndTph.getPairs().add(ag);
allPairs.add(ag);
TPHConstraint con = new ExtendsConstraint(ag.TA1.getName(), ag.TA2.getName(), Relation.EXTENDS);
allCons.add(con);
}
});
}
}
private boolean contains(ArrayList<GenericInsertPair> pairs, GenericInsertPair genPair) {
for(int i=0; i<pairs.size();++i) {
GenericInsertPair p = pairs.get(i);
if(p.TA1.equals(genPair.TA1) && p.TA2.equals(genPair.TA2))
return true;
}
return false;
}
@Override
public void visit(Method method) {
inMethod = true;
methodAndTph = new MethodAndTPH(method.name);
super.visit(method);
inMethod = false;
ListOfMethodsAndTph.add(methodAndTph);
}
}
} }

View File

@ -807,10 +807,12 @@ public class BytecodeGenMethod implements StatementVisitor {
if(methodRefl != null && !methodRefl.getReturnType().isPrimitive()) { if(methodRefl != null && !methodRefl.getReturnType().isPrimitive()) {
if(methodRefl.getReturnType().equals(Object.class)) { if(methodRefl.getReturnType().equals(Object.class)) {
String checkCast = getResolvedType(methodCall.getType()); String checkCast = getResolvedType(methodCall.getType());
int pos = checkCast.length(); if(!checkCast.contains("TPH ")) {
if(checkCast.contains("<")) int pos = checkCast.length();
pos = checkCast.indexOf("<"); if(checkCast.contains("<"))
mv.visitTypeInsn(Opcodes.CHECKCAST,checkCast.substring(0,pos)); pos = checkCast.indexOf("<");
mv.visitTypeInsn(Opcodes.CHECKCAST,checkCast.substring(0,pos));
}
} }
if(isBinaryExp) if(isBinaryExp)
doUnboxing(getResolvedType(methodCall.getType())); doUnboxing(getResolvedType(methodCall.getType()));

View File

@ -0,0 +1,78 @@
/**
*
*/
package de.dhbwstuttgart.bytecode;
import java.util.ArrayList;
import java.util.HashMap;
import de.dhbwstuttgart.bytecode.constraint.ExtendsConstraint;
import de.dhbwstuttgart.bytecode.constraint.TPHConstraint;
import de.dhbwstuttgart.bytecode.constraint.TPHConstraint.Relation;
import de.dhbwstuttgart.bytecode.utilities.MethodAndTPH;
import de.dhbwstuttgart.syntaxtree.AbstractASTWalker;
import de.dhbwstuttgart.syntaxtree.Method;
import de.dhbwstuttgart.syntaxtree.type.TypePlaceholder;
import de.dhbwstuttgart.typeinference.result.GenericInsertPair;
import de.dhbwstuttgart.typeinference.result.ResultSet;
/**
* @author Fayez Abu Alia
*
*/
public class TPHExtractor extends AbstractASTWalker{
// Alle TPHs der Felder werden iKopf der Klasse definiert
// alle TPHs der Klasse: (TPH, is in Method?)
final HashMap<TypePlaceholder,Boolean> allTPHS = new HashMap<>();
MethodAndTPH methodAndTph;
Boolean inMethod = false;
public final ArrayList<MethodAndTPH> ListOfMethodsAndTph = new ArrayList<>();
final ArrayList<GenericInsertPair> allPairs = new ArrayList<>();
public final ArrayList<TPHConstraint> allCons = new ArrayList<>();
private ResultSet resultSet;
public TPHExtractor() {
}
public void setResultSet(ResultSet resultSet) {
this.resultSet = resultSet;
}
@Override
public void visit(TypePlaceholder tph) {
if(resultSet.resolveType(tph).resolvedType instanceof TypePlaceholder) {
TypePlaceholder resolvedTPH = (TypePlaceholder) resultSet.resolveType(tph).resolvedType;
if(inMethod)
methodAndTph.getTphs().add(resolvedTPH.getName());
allTPHS.put(resolvedTPH,inMethod);
resultSet.resolveType(tph).additionalGenerics.forEach(ag ->{
if(ag.contains(resolvedTPH)&&ag.TA1.equals(resolvedTPH)&&!contains(allPairs,ag)) {
if(inMethod)
methodAndTph.getPairs().add(ag);
allPairs.add(ag);
TPHConstraint con = new ExtendsConstraint(ag.TA1.getName(), ag.TA2.getName(), Relation.EXTENDS);
allCons.add(con);
}
});
}
}
private boolean contains(ArrayList<GenericInsertPair> pairs, GenericInsertPair genPair) {
for(int i=0; i<pairs.size();++i) {
GenericInsertPair p = pairs.get(i);
if(p.TA1.equals(genPair.TA1) && p.TA2.equals(genPair.TA2))
return true;
}
return false;
}
@Override
public void visit(Method method) {
inMethod = true;
methodAndTph = new MethodAndTPH(method.name);
super.visit(method);
inMethod = false;
ListOfMethodsAndTph.add(methodAndTph);
}
}

View File

@ -8,14 +8,14 @@ import de.dhbwstuttgart.typeinference.result.GenericInsertPair;
public class MethodAndTPH { public class MethodAndTPH {
private String name; private String name;
private final ArrayList<TypePlaceholder> tphs = new ArrayList<>(); private final ArrayList<String> tphs = new ArrayList<>();
private final ArrayList<GenericInsertPair> pairs = new ArrayList<>(); private final ArrayList<GenericInsertPair> pairs = new ArrayList<>();
public MethodAndTPH(String name) { public MethodAndTPH(String name) {
this.name = name; this.name = name;
} }
public ArrayList<TypePlaceholder> getTphs() { public ArrayList<String> getTphs() {
return tphs; return tphs;
} }

View File

@ -7,7 +7,7 @@ import java.util.LinkedList;
import org.objectweb.asm.Type; import org.objectweb.asm.Type;
import de.dhbwstuttgart.bytecode.BytecodeGen.TPHExtractor; import de.dhbwstuttgart.bytecode.TPHExtractor;
import de.dhbwstuttgart.bytecode.constraint.ExtendsConstraint; import de.dhbwstuttgart.bytecode.constraint.ExtendsConstraint;
import de.dhbwstuttgart.bytecode.constraint.TPHConstraint; import de.dhbwstuttgart.bytecode.constraint.TPHConstraint;
import de.dhbwstuttgart.bytecode.constraint.TPHConstraint.Relation; import de.dhbwstuttgart.bytecode.constraint.TPHConstraint.Relation;
@ -59,13 +59,68 @@ public class Simplify {
System.out.println("----------------"); System.out.println("----------------");
HashMap<TPHConstraint, HashSet<String>> result = new HashMap<>(); HashMap<TPHConstraint, HashSet<String>> result = new HashMap<>();
LinkedList<String> allTypes = new LinkedList<>();
ArrayList<TPHConstraint> constraints = new ArrayList<>(size);
int visited = 0;
for(TPHConstraint c : allCons) {
if(visited >= size)
break;
if(c.getRel() == Relation.EXTENDS) {
++visited;
String left = c.getLeft();
String right = c.getRight();
allTypes.add(left);
allTypes.add(right);
constraints.add(c);
HashMap<String,String> ss1 = new HashMap<>();
for(TPHConstraint constr : allCons) {
ss1.put(constr.getLeft(), constr.getRight());
}
boolean isCycle = false;
while(ss1.containsKey(right)) {
++visited;
String oldRight = right;
right = ss1.get(right);
TPHConstraint toAdd = getConstraint(oldRight, right, allCons);
if(toAdd != null)
constraints.add(toAdd);
if(left.equals(right)) {
isCycle = true;
break;
}
allTypes.add(right);
}
if(isCycle) {
setAllEqual(constraints);
consToRemove.addAll(constraints);
substituteAllTPH(allCons,constraints,left);
HashSet<String> eq = new HashSet<>();
for(String t:allTypes) {
eq.add(t);
}
TPHConstraint constraint = new ExtendsConstraint(left, Type.getInternalName(Object.class), Relation.EXTENDS);
result.put(constraint, eq);
constraints.clear();
allTypes.clear();
}
}
}
for(TPHConstraint c : allCons) { for(TPHConstraint c : allCons) {
if(c.getRel()==Relation.EQUAL) { if(c.getRel()==Relation.EQUAL) {
HashSet<String> equalTPHs = getEqualsTPHs(result, c); if(!isTPHInResEqual(result, c.getLeft())) {
TPHConstraint constraint = getKeyConstraint(result,c); HashSet<String> equalTPHs = getEqualsTPHs(result, c);
equalTPHs.add(c.getLeft()); TPHConstraint constraint = getKeyConstraint(result,c);
equalTPHs.add(c.getRight()); equalTPHs.add(c.getLeft());
result.put(constraint, equalTPHs); equalTPHs.add(c.getRight());
result.put(constraint, equalTPHs);
}
consToRemove.add(c); consToRemove.add(c);
size--; size--;
} }
@ -96,7 +151,7 @@ public class Simplify {
if(c.getRight().equals(Type.getInternalName(Object.class))) if(c.getRight().equals(Type.getInternalName(Object.class)))
size--; size--;
} }
ArrayList<TypePlaceholder> methodTphs = new ArrayList<>(); ArrayList<String> methodTphs = new ArrayList<>();
for(MethodAndTPH m : tphExtractor.ListOfMethodsAndTph) { for(MethodAndTPH m : tphExtractor.ListOfMethodsAndTph) {
if(m.getName().equals(name)) { if(m.getName().equals(name)) {
methodTphs = m.getTphs(); methodTphs = m.getTphs();
@ -185,9 +240,9 @@ public class Simplify {
} }
} }
for(TypePlaceholder tph : methodTphs) { for(String tph : methodTphs) {
if(!isTPHInConstraint(result, tph.getName())) { if(!isTPHInConstraint(result, tph)) {
result.put(new ExtendsConstraint(tph.getName(), Type.getInternalName(Object.class), Relation.EXTENDS), null); result.put(new ExtendsConstraint(tph, Type.getInternalName(Object.class), Relation.EXTENDS), null);
} }
} }
@ -211,6 +266,44 @@ public class Simplify {
return result; return result;
} }
private static TPHConstraint getConstraint(String oldRight, String right, ArrayList<TPHConstraint> allCons) {
for(TPHConstraint c : allCons) {
if(c.getLeft().equals(oldRight) && c.getRight().equals(right))
return c;
}
return null;
}
private static boolean isTPHInResEqual(HashMap<TPHConstraint, HashSet<String>> result, String left) {
for(HashSet<String> eq : result.values()) {
if(eq.contains(left)) {
return true;
}
}
return false;
}
private static void substituteAllTPH(ArrayList<TPHConstraint> allCons, ArrayList<TPHConstraint> constraints,
String first) {
for(TPHConstraint c : allCons) {
for(TPHConstraint c2 : constraints) {
if(c.getRel() == Relation.EXTENDS) {
if(c2.getLeft().equals(c.getLeft()) || c2.getRight().equals(c.getLeft()))
c.setLeft(first);
if(c2.getLeft().equals(c.getRight()) || c2.getRight().equals(c.getRight()))
c.setRight(first);
}
}
}
}
private static void setAllEqual(ArrayList<TPHConstraint> constraints) {
for(TPHConstraint c:constraints) {
c.setRel(Relation.EQUAL);
}
}
public static HashMap<TPHConstraint, HashSet<String>> simplifyContraints(HashMap<TPHConstraint, HashSet<String>> allConstraints) { public static HashMap<TPHConstraint, HashSet<String>> simplifyContraints(HashMap<TPHConstraint, HashSet<String>> allConstraints) {
// 1. check if there are any cycles like L<R and R<L: // 1. check if there are any cycles like L<R and R<L:
// a) yes => set L=R and: // a) yes => set L=R and:
@ -406,9 +499,9 @@ public class Simplify {
return false; return false;
} }
private static boolean containTPH(ArrayList<TypePlaceholder> methodTphs, String sub) { private static boolean containTPH(ArrayList<String> methodTphs, String sub) {
for(TypePlaceholder tph : methodTphs) { for(String tph : methodTphs) {
if(tph.getName().equals(sub)) if(tph.equals(sub))
return true; return true;
} }
return false; return false;

View File

@ -1,4 +0,0 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

View File

@ -32,22 +32,24 @@ public class FacultyTest {
loader = new URLClassLoader(new URL[] {new URL("file://"+pathToClassFile)}); loader = new URLClassLoader(new URL[] {new URL("file://"+pathToClassFile)});
classToTest = loader.loadClass("Faculty"); classToTest = loader.loadClass("Faculty");
instanceOfClass = classToTest.getDeclaredConstructor().newInstance(); instanceOfClass = classToTest.getDeclaredConstructor().newInstance();
// Method m = classToTest.getDeclaredMethod("m", Integer.class); Method getFact = classToTest.getDeclaredMethod("getFact", Integer.class);
Field fact = classToTest.getDeclaredField("fact"); // Field fact = classToTest.getDeclaredField("fact");
// Class<?> lambda = m.invoke(instanceOfClass).getClass(); // Class<?> lambda = m.invoke(instanceOfClass).getClass();
Class<?> lambda = fact.getType(); // Class<?> lambda = fact.getType();
Method apply = lambda.getMethod("apply", Object.class); // System.out.println(fact.getType().getName());
// // Method apply = lambda.getMethod("apply", Object.class);
// System.out.println(lambda.getMethods()[0]);
// System.out.println(instanceOfClass.toString());
// // Damit man auf die Methode zugreifen kann // // Damit man auf die Methode zugreifen kann
apply.setAccessible(true); // apply.setAccessible(true);
// Field value // Field value
Object fieldVal = fact.get(instanceOfClass); // Object fieldVal = fact.get(instanceOfClass);
Integer i = 3; Integer i = 3;
Method applyFromField = fieldVal.getClass().getDeclaredMethod("apply", Object.class); // Method applyFromField = fieldVal.getClass().getDeclaredMethod("apply", Object.class);
applyFromField.setAccessible(true); // applyFromField.setAccessible(true);
Integer result = (Integer) applyFromField.invoke(instanceOfClass, i); // Integer result = (Integer) apply.invoke(lambda,i);
// Integer result = (Integer) m.invoke(instanceOfClass,i); Integer result = (Integer) getFact.invoke(instanceOfClass,i);
assertEquals(6, result); assertEquals(6, result);
} }

View File

@ -40,7 +40,7 @@ public class LambdaTest {
apply.setAccessible(true); apply.setAccessible(true);
Integer i = 77; Integer i = 77;
System.out.println(m.invoke(instanceOfClass).toString());
Integer result = (Integer) apply.invoke(m.invoke(instanceOfClass), i); Integer result = (Integer) apply.invoke(m.invoke(instanceOfClass), i);
assertEquals(77, result); assertEquals(77, result);

View File

@ -13,6 +13,10 @@ public class Faculty {
}; };
} }
public getFact(x) {
return fact.apply(x);
}
// m (x) { // m (x) {
// //
//// var fact = (x) -> { //// var fact = (x) -> {

View File

@ -2,8 +2,8 @@ public class Tph {
m(a,b){ m(a,b){
var c = m2(b); var c = m2(b);
return a; // return a;
// return m2(b); return m2(b);
} }
m2(b){ m2(b){

View File

@ -0,0 +1,76 @@
/**
*
*/
package bytecode.simplifyalgo;
import static org.junit.Assert.*;
import java.util.HashMap;
import java.util.HashSet;
import org.junit.BeforeClass;
import org.junit.Test;
import org.objectweb.asm.Type;
import de.dhbwstuttgart.bytecode.TPHExtractor;
import de.dhbwstuttgart.bytecode.constraint.ExtendsConstraint;
import de.dhbwstuttgart.bytecode.constraint.TPHConstraint;
import de.dhbwstuttgart.bytecode.constraint.TPHConstraint.Relation;
import de.dhbwstuttgart.bytecode.utilities.MethodAndTPH;
import de.dhbwstuttgart.bytecode.utilities.Simplify;
/**
* @author Fayez Abu Alia
*
*/
public class CycleTest {
private static TPHExtractor tphExtractor;
private static String methName;
/**
* @throws java.lang.Exception
*/
@BeforeClass
public static void setUpBeforeClass() throws Exception {
tphExtractor = new TPHExtractor();
// A < B
TPHConstraint c1 = new ExtendsConstraint("A", "B", Relation.EXTENDS);
// B < C
TPHConstraint c2 = new ExtendsConstraint("B", "C", Relation.EXTENDS);
// C < D
TPHConstraint c3 = new ExtendsConstraint("C", "D", Relation.EXTENDS);
// D < A
TPHConstraint c4 = new ExtendsConstraint("D", "A", Relation.EXTENDS);
// name
methName = "m";
MethodAndTPH mtph = new MethodAndTPH("m");
mtph.getTphs().add("A");
mtph.getTphs().add("B");
mtph.getTphs().add("C");
mtph.getTphs().add("D");
tphExtractor.ListOfMethodsAndTph.add(mtph);
tphExtractor.allCons.add(c1);
tphExtractor.allCons.add(c2);
tphExtractor.allCons.add(c3);
tphExtractor.allCons.add(c4);
}
@Test
public void test() {
HashMap<TPHConstraint, HashSet<String>> result = new HashMap<>();
HashSet<String> equals = new HashSet<>();
equals.add("A");
equals.add("B");
equals.add("C");
equals.add("D");
TPHConstraint k = new ExtendsConstraint("A", Type.getInternalName(Object.class), Relation.EXTENDS);
result.put(k, equals);
HashMap<TPHConstraint, HashSet<String>> sim = Simplify.simplifyConstraints(methName, tphExtractor);
boolean areEquals = SimpleCycle.areMapsEqual(result, sim);
assertTrue(areEquals);
}
}

View File

@ -0,0 +1,48 @@
package bytecode.simplifyalgo;
import static org.junit.Assert.*;
import org.junit.BeforeClass;
import org.junit.Test;
import de.dhbwstuttgart.bytecode.TPHExtractor;
import de.dhbwstuttgart.bytecode.constraint.ExtendsConstraint;
import de.dhbwstuttgart.bytecode.constraint.TPHConstraint;
import de.dhbwstuttgart.bytecode.constraint.TPHConstraint.Relation;
import de.dhbwstuttgart.bytecode.utilities.MethodAndTPH;
import de.dhbwstuttgart.typedeployment.TypeInsertPlacer;
/**
*
* @author Fayez Abu Alia
*
*/
public class SameLeftSide {
// Typeplaceholders können nicht definiert werden, da die Konstruktor
// private ist => Test geht nicht
private static TPHExtractor tphExtractor;
private static String methName;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
tphExtractor = new TPHExtractor();
// A < B
TPHConstraint c1 = new ExtendsConstraint("A", "B", Relation.EXTENDS);
// A < C
TPHConstraint c2 = new ExtendsConstraint("A", "C", Relation.EXTENDS);
// B < D
TPHConstraint c3 = new ExtendsConstraint("B", "D", Relation.EXTENDS);
// C < E
TPHConstraint c4 = new ExtendsConstraint("C", "E", Relation.EXTENDS);
// name
methName = "m1";
MethodAndTPH m1 = new MethodAndTPH("m1");
// tphExtractor.ListOfMethodsAndTph.add(e)
tphExtractor.allCons.add(c1);
tphExtractor.allCons.add(c2);
}
}

View File

@ -0,0 +1,78 @@
package bytecode.simplifyalgo;
import static org.junit.Assert.*;
import java.util.HashMap;
import java.util.HashSet;
import org.junit.BeforeClass;
import org.junit.Test;
import org.objectweb.asm.Type;
import de.dhbwstuttgart.bytecode.TPHExtractor;
import de.dhbwstuttgart.bytecode.constraint.ExtendsConstraint;
import de.dhbwstuttgart.bytecode.constraint.TPHConstraint;
import de.dhbwstuttgart.bytecode.constraint.TPHConstraint.Relation;
import de.dhbwstuttgart.bytecode.utilities.Simplify;
/**
*
* @author Fayez Abu Alia
*
*/
public class SimpleCycle {
private static TPHExtractor tphExtractor;
private static String methName;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
tphExtractor = new TPHExtractor();
// A < B
TPHConstraint c1 = new ExtendsConstraint("A", "B", Relation.EXTENDS);
// B < A
TPHConstraint c2 = new ExtendsConstraint("B", "A", Relation.EXTENDS);
// name
methName = "m";
tphExtractor.allCons.add(c1);
tphExtractor.allCons.add(c2);
}
@Test
public void test() {
HashMap<TPHConstraint, HashSet<String>> result = new HashMap<>();
HashSet<String> equals = new HashSet<>();
equals.add("A");
equals.add("B");
TPHConstraint k = new ExtendsConstraint("B", Type.getInternalName(Object.class), Relation.EXTENDS);
result.put(k, equals);
HashMap<TPHConstraint, HashSet<String>> sim = Simplify.simplifyConstraints(methName, tphExtractor);
boolean areEquals = areMapsEqual(result, sim);
assertTrue(areEquals);
}
public static boolean areMapsEqual(HashMap<TPHConstraint, HashSet<String>> m1, HashMap<TPHConstraint, HashSet<String>> m2) {
for(TPHConstraint c : m1.keySet()) {
for(TPHConstraint c2 : m2.keySet()) {
if(c.getLeft().equals(c2.getLeft()) && c.getRight().equals(c2.getRight()) && c.getRel()==c2.getRel()) {
HashSet<String> eq1 = m1.get(c);
HashSet<String> eq2 = m2.get(c2);
if(eq1.size() != eq2.size())
return false;
for(String tph:eq1) {
if(!eq2.contains(tph))
return false;
}
}else {
return false;
}
}
}
return true;
}
}