/*
* Copyright 2012, Thomas Kerber
*
* 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 milk.jpatch.attribs;
import java.util.ArrayList;
import java.util.List;
import org.apache.bcel.classfile.AnnotationEntry;
import org.apache.bcel.classfile.Annotations;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.RuntimeInvisibleAnnotations;
import org.apache.bcel.classfile.RuntimeVisibleAnnotations;
import milk.jpatch.CPoolMap;
import milk.jpatch.Util;
/**
* Patches a "RuntimeVisibleAnnotations" or "RuntimeInvisibleAnnotations"
* attribute.
*
* Merges with previous.
* @author Thomas Kerber
* @version 1.0.1
*/
public class AnnotationsPatch extends AttributePatch{
private static final long serialVersionUID = 3771862719685986222L;
/**
* The annotations to be added.
*/
private final AnnotationEntry[] annotsAdd;
/**
* The annotations to be removed.
*/
private final AnnotationEntry[] annotsRem;
/**
* Whether or not runtime annotations are described by this patch.
*/
public final boolean isRuntime;
/**
* Creates a new patch.
* @param annotsAdd The annotations to add.
* @param annotsRem The annotations to remove.
* @param isRuntime Whether or not the annotations are runtime.
*/
public AnnotationsPatch(AnnotationEntry[] annotsAdd,
AnnotationEntry[] annotsRem, boolean isRuntime){
this.annotsAdd = annotsAdd;
this.annotsRem = annotsRem;
this.isRuntime = isRuntime;
}
/**
* Generates the patch.
* @param old The old Attributes.
* @param new_ The new Attributes.
* @param patches The patches already generated.
*/
public static void generate(Attribute[] old, Attribute[] new_,
List<AttributePatch> patches){
// One patch for runtime, one for non-runtime.
for(boolean isRuntime : new boolean[]{true, false}){
// Find old Annotations
Annotations oldAnnots = null;
for(int i = 0; i < old.length; i++){
boolean correct = false;
correct |= isRuntime &&
old[i] instanceof RuntimeVisibleAnnotations;
correct |= !isRuntime &&
old[i] instanceof RuntimeInvisibleAnnotations;
if(correct){
oldAnnots = (Annotations)old[i];
break;
}
}
//Find new Annotations
Annotations newAnnots = null;
for(int i = 0; i < new_.length; i++){
boolean correct = false;
correct |= isRuntime &&
new_[i] instanceof RuntimeVisibleAnnotations;
correct |= !isRuntime &&
new_[i] instanceof RuntimeInvisibleAnnotations;
if(correct){
newAnnots = (Annotations)new_[i];
break;
}
}
AnnotationEntry[] oldEntries = oldAnnots == null ?
new AnnotationEntry[0] :
oldAnnots.getAnnotationEntries();
AnnotationEntry[] newEntries = newAnnots == null ?
new AnnotationEntry[0] :
newAnnots.getAnnotationEntries();
// Finds entries which are new.
List<AnnotationEntry> addedEntries =
new ArrayList<AnnotationEntry>();
outer: for(AnnotationEntry newAe : newEntries){
for(AnnotationEntry oldAe : oldEntries){
if(Util.equals(newAe, oldAe))
continue outer;
}
addedEntries.add(newAe);
}
// Finds entries which got removed.
List<AnnotationEntry> removedEntries =
new ArrayList<AnnotationEntry>();
outer: for(AnnotationEntry oldAe : oldEntries){
for(AnnotationEntry newAe : newEntries){
if(Util.equals(newAe, oldAe))
continue outer;
}
removedEntries.add(oldAe);
}
// Nothing to do here.
if(addedEntries.size() == 0 && removedEntries.size() == 0)
return;
// Patches.
patches.add(new AnnotationsPatch(
addedEntries.toArray(
new AnnotationEntry[addedEntries.size()]),
removedEntries.toArray(
new AnnotationEntry[removedEntries.size()]),
isRuntime));
}
}
/**
*
* @return The annotations which are added by this patch.
*/
public AnnotationEntry[] getAddedAnnotations(){
return annotsAdd;
}
/**
*
* @return The annotations which are removed by this patch.
*/
public AnnotationEntry[] getRemovedAnnotations(){
return annotsRem;
}
@Override
public List<Attribute> patch(List<Attribute> attribs, CPoolMap map){
AnnotationEntry[] newAnnots = new AnnotationEntry[annotsAdd.length];
for(int i = 0; i < newAnnots.length; i++)
newAnnots[i] = map.applyTo(annotsAdd[i]);
AnnotationEntry[] remAnnots = new AnnotationEntry[annotsRem.length];
for(int i = 0; i < remAnnots.length; i++)
remAnnots[i] = map.applyTo(annotsRem[i]);
int index = findName(
attribs,
isRuntime ?
"RuntimeVisibleAnnotations" :
"RuntimeInvisibleAnnotations");
AnnotationEntry[] oldAnnots;
if(index == -1)
oldAnnots = new AnnotationEntry[0];
else
oldAnnots = ((Annotations)attribs.get(index)).
getAnnotationEntries();
List<AnnotationEntry> annots = new ArrayList<AnnotationEntry>();
// Add old, except the removed
outer: for(AnnotationEntry e : oldAnnots){
if(e == null)
continue;
for(AnnotationEntry r : remAnnots){
if(Util.equals(e, r))
continue outer;
}
annots.add(e);
}
// Add new
for(AnnotationEntry e : newAnnots){
if(e == null)
continue;
annots.add(e);
}
int length = 2; // 2 bytes apart from the annotations themselves.
boolean empty = true;
for(AnnotationEntry e : annots){
length += Util.getLength(e);
empty = false;
}
if(empty)
return attribs;
Annotations new_;
if(isRuntime)
new_ = new RuntimeVisibleAnnotations(
Util.findConstantStringIn(map,
"RuntimeVisibleAnnotations"),
length,
annots.toArray(new AnnotationEntry[annots.size()]),
map.to);
else
new_ = new RuntimeInvisibleAnnotations(
Util.findConstantStringIn(map,
"RuntimeInvisibleAnnotations"),
length,
annots.toArray(new AnnotationEntry[annots.size()]),
map.to);
return replaceTypeWith(
attribs,
isRuntime ?
"RuntimeVisibleAnnotations" :
"RuntimeInvisibleAnnotions",
new_);
}
}