/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. Alternatively, the contents of this file may be used under
* the terms of the GNU Lesser General Public License Version 2.1 or later,
* or the Apache License Version 2.0.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*/
package javassist.bytecode.analysis;
import java.util.
HashMap;
import java.util.
Iterator;
import java.util.
Map;
import javassist.
CtClass;
/**
* MultiType represents an unresolved type. Whenever two {@code Type}
* instances are merged, if they share more than one super type (either an
* interface or a superclass), then a {@code MultiType} is used to
* represent the possible super types. The goal of a {@code MultiType}
* is to reduce the set of possible types down to a single resolved type. This
* is done by eliminating non-assignable types from the typeset when the
* {@code MultiType} is passed as an argument to
* {@link Type#isAssignableFrom(Type)}, as well as removing non-intersecting
* types during a merge.
*
* Note: Currently the {@code MultiType} instance is reused as much
* as possible so that updates are visible from all frames. In addition, all
* {@code MultiType} merge paths are also updated. This is somewhat
* hackish, but it appears to handle most scenarios.
*
* @author Jason T. Greene
*/
/* TODO - A better, but more involved, approach would be to track the instruction
* offset that resulted in the creation of this type, and
* whenever the typeset changes, to force a merge on that position. This
* would require creating a new MultiType instance every time the typeset
* changes, and somehow communicating assignment changes to the Analyzer
*/
public class
MultiType extends
Type {
private
Map interfaces;
private
Type resolved;
private
Type potentialClass;
private
MultiType mergeSource;
private boolean
changed = false;
public
MultiType(
Map interfaces) {
this(
interfaces, null);
}
public
MultiType(
Map interfaces,
Type potentialClass) {
super(null);
this.
interfaces =
interfaces;
this.
potentialClass =
potentialClass;
}
/**
* Gets the class that corresponds with this type. If this information
* is not yet known, java.lang.Object will be returned.
*/
public
CtClass getCtClass() {
if (
resolved != null)
return
resolved.
getCtClass();
return
Type.
OBJECT.
getCtClass();
}
/**
* Always returns null since this type is never used for an array.
*/
public
Type getComponent() {
return null;
}
/**
* Always returns 1, since this type is a reference.
*/
public int
getSize() {
return 1;
}
/**
* Always reutnrs false since this type is never used for an array
*/
public boolean
isArray() {
return false;
}
/**
* Returns true if the internal state has changed.
*/
boolean
popChanged() {
boolean
changed = this.
changed;
this.
changed = false;
return
changed;
}
public boolean
isAssignableFrom(
Type type) {
throw new
UnsupportedOperationException("Not implemented");
}
public boolean
isAssignableTo(
Type type) {
if (
resolved != null)
return
type.
isAssignableFrom(
resolved);
if (
Type.
OBJECT.
equals(
type))
return true;
if (
potentialClass != null && !
type.
isAssignableFrom(
potentialClass))
potentialClass = null;
Map map =
mergeMultiAndSingle(this,
type);
if (
map.
size() == 1 &&
potentialClass == null) {
// Update previous merge paths to the same resolved type
resolved =
Type.
get((
CtClass)
map.
values().
iterator().
next());
propogateResolved();
return true;
}
// Keep all previous merge paths up to date
if (
map.
size() >= 1) {
interfaces =
map;
propogateState();
return true;
}
if (
potentialClass != null) {
resolved =
potentialClass;
propogateResolved();
return true;
}
return false;
}
private void
propogateState() {
MultiType source =
mergeSource;
while (
source != null) {
source.
interfaces =
interfaces;
source.
potentialClass =
potentialClass;
source =
source.
mergeSource;
}
}
private void
propogateResolved() {
MultiType source =
mergeSource;
while (
source != null) {
source.
resolved =
resolved;
source =
source.
mergeSource;
}
}
/**
* Always returns true, since this type is always a reference.
*
* @return true
*/
public boolean
isReference() {
return true;
}
private
Map getAllMultiInterfaces(
MultiType type) {
Map map = new
HashMap();
Iterator iter =
type.
interfaces.
values().
iterator();
while (
iter.
hasNext()) {
CtClass intf = (
CtClass)
iter.
next();
map.
put(
intf.
getName(),
intf);
getAllInterfaces(
intf,
map);
}
return
map;
}
private
Map mergeMultiInterfaces(
MultiType type1,
MultiType type2) {
Map map1 =
getAllMultiInterfaces(
type1);
Map map2 =
getAllMultiInterfaces(
type2);
return
findCommonInterfaces(
map1,
map2);
}
private
Map mergeMultiAndSingle(
MultiType multi,
Type single) {
Map map1 =
getAllMultiInterfaces(
multi);
Map map2 =
getAllInterfaces(
single.
getCtClass(), null);
return
findCommonInterfaces(
map1,
map2);
}
private boolean
inMergeSource(
MultiType source) {
while (
source != null) {
if (
source == this)
return true;
source =
source.
mergeSource;
}
return false;
}
public
Type merge(
Type type) {
if (this ==
type)
return this;
if (
type ==
UNINIT)
return this;
if (
type ==
BOGUS)
return
BOGUS;
if (
type == null)
return this;
if (
resolved != null)
return
resolved.
merge(
type);
if (
potentialClass != null) {
Type mergePotential =
potentialClass.
merge(
type);
if (!
mergePotential.
equals(
potentialClass) ||
mergePotential.
popChanged()) {
potentialClass =
Type.
OBJECT.
equals(
mergePotential) ? null :
mergePotential;
changed = true;
}
}
Map merged;
if (
type instanceof
MultiType) {
MultiType multi = (
MultiType)
type;
if (
multi.
resolved != null) {
merged =
mergeMultiAndSingle(this,
multi.
resolved);
} else {
merged =
mergeMultiInterfaces(
multi, this);
if (!
inMergeSource(
multi))
mergeSource =
multi;
}
} else {
merged =
mergeMultiAndSingle(this,
type);
}
// Keep all previous merge paths up to date
if (
merged.
size() > 1 || (
merged.
size() == 1 &&
potentialClass != null)) {
// Check for changes
if (
merged.
size() !=
interfaces.
size()) {
changed = true;
} else if (
changed == false){
Iterator iter =
merged.
keySet().
iterator();
while (
iter.
hasNext())
if (!
interfaces.
containsKey(
iter.
next()))
changed = true;
}
interfaces =
merged;
propogateState();
return this;
}
if (
merged.
size() == 1) {
resolved =
Type.
get((
CtClass)
merged.
values().
iterator().
next());
} else if (
potentialClass != null){
resolved =
potentialClass;
} else {
resolved =
OBJECT;
}
propogateResolved();
return
resolved;
}
public boolean
equals(
Object o) {
if (! (
o instanceof
MultiType))
return false;
MultiType multi = (
MultiType)
o;
if (
resolved != null)
return
resolved.
equals(
multi.
resolved);
else if (
multi.
resolved != null)
return false;
return
interfaces.
keySet().
equals(
multi.
interfaces.
keySet());
}
public
String toString() {
if (
resolved != null)
return
resolved.
toString();
StringBuffer buffer = new
StringBuffer("{");
Iterator iter =
interfaces.
keySet().
iterator();
while (
iter.
hasNext()) {
buffer.
append(
iter.
next());
buffer.
append(", ");
}
buffer.
setLength(
buffer.
length() - 2);
if (
potentialClass != null)
buffer.
append(", *").
append(
potentialClass.
toString());
buffer.
append("}");
return
buffer.
toString();
}
}