diff --git a/src/main/java/com/sk89q/worldguard/region/Region.java b/src/main/java/com/sk89q/worldguard/region/Region.java index e17cc9bb..573ca089 100644 --- a/src/main/java/com/sk89q/worldguard/region/Region.java +++ b/src/main/java/com/sk89q/worldguard/region/Region.java @@ -23,6 +23,7 @@ import java.util.Map; import org.apache.commons.lang.Validate; +import com.sk89q.worldguard.region.attribute.Attribute; import com.sk89q.worldguard.region.shapes.IndexableShape; /** diff --git a/src/main/java/com/sk89q/worldguard/region/Attribute.java b/src/main/java/com/sk89q/worldguard/region/attribute/Attribute.java similarity index 89% rename from src/main/java/com/sk89q/worldguard/region/Attribute.java rename to src/main/java/com/sk89q/worldguard/region/attribute/Attribute.java index bea92460..87c8ffa5 100644 --- a/src/main/java/com/sk89q/worldguard/region/Attribute.java +++ b/src/main/java/com/sk89q/worldguard/region/attribute/Attribute.java @@ -16,7 +16,7 @@ * this program. If not, see . */ -package com.sk89q.worldguard.region; +package com.sk89q.worldguard.region.attribute; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -24,6 +24,8 @@ import java.io.IOException; import org.apache.commons.lang.Validate; +import com.sk89q.worldguard.region.Region; + /** * Attributes can be attached to regions to store all sorts of data. *

@@ -36,16 +38,16 @@ import org.apache.commons.lang.Validate; * *

* Asking for attributes from a region will return a subclass of - * this class, which can be either an instance of {@link DataValuedAttribute} + * this class, which can be either an instance of {@link ByteArray} * or a custom overriding attribute class available in WorldGuard or * from another project. *

* Creating your attributes to store any type of data is easy. The most - * important decision to make is whether to use {@link DataValuedAttribute} + * important decision to make is whether to use {@link ByteArray} * or your own subclass. The latter lets you store the attribute data * in a structure more native to your software (using primitive data types and * your own objects), but it may be not be necessary if your payload does - * consist of raw binary data, which is what {@link DataValuedAttribute} does. + * consist of raw binary data, which is what {@link ByteArray} does. *

* When you make a subclass, you must define the * {@link #read(DataInputStream, int)} and @@ -62,7 +64,7 @@ import org.apache.commons.lang.Validate; * the classpath, as the canonical name of each attribute's class is saved * during saving. The process involved here is outlined more closely on * the WorldGuard wiki. If worse comes to worse, and the subclass cannot - * be found, then the data will be loaded as an {@link DataValuedAttribute}, + * be found, then the data will be loaded as an {@link ByteArray}, * which prevents loss of the raw binary data, but it will render the * data unusuable during runtime unless explicitly deserialization is * conducted when recalling the attribute. @@ -72,9 +74,10 @@ public abstract class Attribute { private String name = "unnamed"; /** - * A no-arg constructor required for automatic instantiation of - * attributes. If you are overriding this class, you must provide - * this constructor. + * A constructor required for automatic instantiation of attributes. + *

+ * If you are overriding this class, you must provide this constructor. + * Be aware that the default name of attributes is 'unnamed'. */ public Attribute() { } @@ -134,7 +137,7 @@ public abstract class Attribute { * @param len length of the data * @throw IOException thrown on read error, which would cause the instance * of this class to fail, causing the region index to - * fall back to {@link DataValuedAttribute} + * fall back to {@link ByteArray} */ public abstract void read(DataInputStream in, int len) throws IOException; diff --git a/src/main/java/com/sk89q/worldguard/region/DataValuedAttribute.java b/src/main/java/com/sk89q/worldguard/region/attribute/ByteArray.java similarity index 60% rename from src/main/java/com/sk89q/worldguard/region/DataValuedAttribute.java rename to src/main/java/com/sk89q/worldguard/region/attribute/ByteArray.java index 8f4c5cad..bd69dff5 100644 --- a/src/main/java/com/sk89q/worldguard/region/DataValuedAttribute.java +++ b/src/main/java/com/sk89q/worldguard/region/attribute/ByteArray.java @@ -16,7 +16,7 @@ * this program. If not, see . */ -package com.sk89q.worldguard.region; +package com.sk89q.worldguard.region.attribute; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -25,32 +25,31 @@ import java.io.IOException; import org.apache.commons.lang.Validate; /** - * A simple implementation of {@link Attribute} that saves the raw binary - * data stream for exact recall later during runtime and during serialization. + * Stores raw byte array data. *

- * By itself, this class will accept binary data, store it as-is, then - * dump it back out as binary data when requested. If you have special needs, - * and do not want to have to explicitly deserialize the contents contained - * within this attribute, consider subclassing {@link Attribute} instead. - *

- * This is the automatic fallback attribute in case custom attribute - * classes are unavailable during load. + * If no more accurate {@link Attribute} class is found during deserialization, + * this class is used because it would maintain the data. If you wish to create + * your own {@link Attribute}s, consider subclassing that class rather than + * using this class. + * + * @see Managed another way to store byte arrays (with more memory cost and null + * support) */ -public final class DataValuedAttribute extends Attribute { +public final class ByteArray extends Attribute { - private byte[] buffer; + private byte[] value; /** * Construct the attribute with a default name. */ - public DataValuedAttribute() { + public ByteArray() { super(); } /** * Construct the attribute with a given name. */ - public DataValuedAttribute(String name) { + public ByteArray(String name) { super(name); } @@ -62,23 +61,21 @@ public final class DataValuedAttribute extends Attribute { * * @return data */ - public byte[] getByteArray() { - return buffer; + public byte[] getValue() { + return value; } /** * Set the raw byte array. *

- * The given byte array is not copied and so further modifications to the - * given byte array will have the changes reflect on the byte array - * inside this object. + * The given byte array is stored as a reference within this instance. * * @param data new data */ - public void setByteArray(byte[] data) { - Validate.notNull(data); + public void setValue(byte[] value) { + Validate.notNull(value); - this.buffer = data; + this.value = value; } /** @@ -87,19 +84,19 @@ public final class DataValuedAttribute extends Attribute { * @return length in bytes of data */ public int size() { - return buffer.length; + return value.length; } @Override public void read(DataInputStream in, int len) throws IOException { byte[] buffer = new byte[len]; in.read(buffer, 0, len); - this.buffer = buffer; + this.value = buffer; } @Override public void write(DataOutputStream out) throws IOException { - out.write(buffer); + out.write(value); } } diff --git a/src/main/java/com/sk89q/worldguard/region/attribute/Flag.java b/src/main/java/com/sk89q/worldguard/region/attribute/Flag.java new file mode 100644 index 00000000..8355917f --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/region/attribute/Flag.java @@ -0,0 +1,85 @@ +// $Id$ +/* + * This file is a part of WorldGuard. + * Copyright (c) sk89q + * Copyright (c) the WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY), without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see . +*/ + +package com.sk89q.worldguard.region.attribute; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * An attribute that stores two states (booleans). + * + * @see Managed another way to store booleans (with more memory cost and null + * support) + */ +public class Flag extends Attribute { + + private boolean value; + + /** + * Construct an instance and assign a default name. + * + * @see Attribute#Attribute() for basic mechanics + */ + public Flag() { + } + + /** + * Construct an instance and specify an attribute name. + * + * @param name name of the attribute + */ + public Flag(String name) { + super(name); + } + + /** + * Get the value. + * + * @return the value + */ + public boolean getValue() { + return value; + } + + /** + * Set the value. + * + * @param value the new value + */ + public void setValue(boolean value) { + this.value = value; + } + + @Override + public void read(DataInputStream in, int len) throws IOException { + value = in.readBoolean(); + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeBoolean(value); + } + + @Override + public String toString() { + return String.valueOf(value); + } + +} diff --git a/src/main/java/com/sk89q/worldguard/region/StringAttr.java b/src/main/java/com/sk89q/worldguard/region/attribute/Fraction.java similarity index 64% rename from src/main/java/com/sk89q/worldguard/region/StringAttr.java rename to src/main/java/com/sk89q/worldguard/region/attribute/Fraction.java index 69d8a3f2..2cd56abc 100644 --- a/src/main/java/com/sk89q/worldguard/region/StringAttr.java +++ b/src/main/java/com/sk89q/worldguard/region/attribute/Fraction.java @@ -16,25 +16,28 @@ * this program. If not, see . */ -package com.sk89q.worldguard.region; +package com.sk89q.worldguard.region.attribute; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import org.apache.commons.lang.Validate; - /** - * An attribute that stores string data. + * An attribute that stores doubles. + * + * @see Managed another way to store numbers (with more memory cost and null + * support) */ -public class StringAttr extends Attribute { +public class Fraction extends Attribute { - private String data; + private double value; /** - * No-arg constructor. + * Construct an instance and assign a default name. + * + * @see Attribute#Attribute() for basic mechanics */ - public StringAttr() { + public Fraction() { } /** @@ -42,43 +45,41 @@ public class StringAttr extends Attribute { * * @param name name of the attribute */ - public StringAttr(String name) { + public Fraction(String name) { super(name); } /** - * Get the text. + * Get the value. * - * @return the text + * @return the value */ - public String getText() { - return data; + public double getValue() { + return value; } /** - * Set the stored text. + * Set the value. * - * @param text the new text + * @param value the new value */ - public void setText(String text) { - Validate.notNull(text); - - this.data = text; + public void setValue(double value) { + this.value = value; } @Override public void read(DataInputStream in, int len) throws IOException { - data = in.readUTF(); + value = in.readDouble(); } @Override public void write(DataOutputStream out) throws IOException { - out.writeUTF(data); + out.writeDouble(value); } @Override public String toString() { - return data; + return String.valueOf(value); } } diff --git a/src/main/java/com/sk89q/worldguard/region/attribute/Managed.java b/src/main/java/com/sk89q/worldguard/region/attribute/Managed.java new file mode 100644 index 00000000..e36a6b19 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/region/attribute/Managed.java @@ -0,0 +1,96 @@ +// $Id$ +/* + * This file is a part of WorldGuard. + * Copyright (c) sk89q + * Copyright (c) the WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY), without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see . +*/ + +package com.sk89q.worldguard.region.attribute; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * An attribute that serializes and de-serializes objects automatically + * using Java's serialization methods. + *

+ * This class allows for null values. + */ +public class Managed extends Attribute { + + private T value; + + /** + * Construct an instance and assign a default name. + * + * @see Attribute#Attribute() for basic mechanics + */ + public Managed() { + } + + /** + * Construct an instance and specify an attribute name. + * + * @param name name of the attribute + */ + public Managed(String name) { + super(name); + } + + /** + * Get the value. + * + * @return the value or null + */ + public T getValue() { + return value; + } + + /** + * Set the value. + * + * @param value the new value or null + */ + public void setValue(T value) { + this.value = value; + } + + @SuppressWarnings("unchecked") + @Override + public void read(DataInputStream in, int len) throws IOException { + ObjectInputStream objIn = new ObjectInputStream(in); + try { + value = (T) objIn.readObject(); + } catch (ClassCastException e) { + throw new IOException("Deserialized object was something else", e); + } catch (ClassNotFoundException e) { + throw new IOException("Deserialized object uses class not found", e); + } + } + + @Override + public void write(DataOutputStream out) throws IOException { + ObjectOutputStream objOut = new ObjectOutputStream(out); + objOut.writeObject(out); + } + + @Override + public String toString() { + return String.valueOf(value); + } + +} diff --git a/src/main/java/com/sk89q/worldguard/region/attribute/Message.java b/src/main/java/com/sk89q/worldguard/region/attribute/Message.java new file mode 100644 index 00000000..91ca9557 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/region/attribute/Message.java @@ -0,0 +1,93 @@ +// $Id$ +/* + * This file is a part of WorldGuard. + * Copyright (c) sk89q + * Copyright (c) the WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY), without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see . +*/ + +package com.sk89q.worldguard.region.attribute; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import org.apache.commons.lang.Validate; + +/** + * An attribute that stores strings. + *

+ * Compared to {@link Managed}, strings stored with this class use less + * space and memory. Be aware that strings stored with this class + * are limited to the size of an unsigned short. + * + * @see Managed another way to store strings (with more memory cost and null + * support) + */ +public class Message extends Attribute { + + private String value = ""; + + /** + * Construct an instance and assign a default name. + * + * @see Attribute#Attribute() for basic mechanics + */ + public Message() { + } + + /** + * Construct an instance and specify an attribute name. + * + * @param name name of the attribute + */ + public Message(String name) { + super(name); + } + + /** + * Get the value. + * + * @return the value + */ + public String getValue() { + return value; + } + + /** + * Set the value. + * + * @param value the new value (cannot be null) + */ + public void setValue(String value) { + Validate.notNull(value); + + this.value = value; + } + + @Override + public void read(DataInputStream in, int len) throws IOException { + value = in.readUTF(); + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeUTF(value); + } + + @Override + public String toString() { + return String.valueOf(value); + } + +} diff --git a/src/main/java/com/sk89q/worldguard/region/attribute/Number.java b/src/main/java/com/sk89q/worldguard/region/attribute/Number.java new file mode 100644 index 00000000..2a0f0a84 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/region/attribute/Number.java @@ -0,0 +1,86 @@ +// $Id$ +/* + * This file is a part of WorldGuard. + * Copyright (c) sk89q + * Copyright (c) the WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY), without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see . +*/ + +package com.sk89q.worldguard.region.attribute; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * An attribute that stores whole numbers (integers) with a range of + * {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE}. + * + * @see Managed another way to store numbers (with more memory cost and null + * support) + */ +public class Number extends Attribute { + + private int value; + + /** + * Construct an instance and assign a default name. + * + * @see Attribute#Attribute() for basic mechanics + */ + public Number() { + } + + /** + * Construct an instance and specify an attribute name. + * + * @param name name of the attribute + */ + public Number(String name) { + super(name); + } + + /** + * Get the value. + * + * @return the value + */ + public int getValue() { + return value; + } + + /** + * Set the value. + * + * @param value the new value + */ + public void setValue(int value) { + this.value = value; + } + + @Override + public void read(DataInputStream in, int len) throws IOException { + value = in.readInt(); + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.write(value); + } + + @Override + public String toString() { + return String.valueOf(value); + } + +} diff --git a/src/main/java/com/sk89q/worldguard/region/attribute/State.java b/src/main/java/com/sk89q/worldguard/region/attribute/State.java new file mode 100644 index 00000000..6eda562b --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/region/attribute/State.java @@ -0,0 +1,111 @@ +// $Id$ +/* + * This file is a part of WorldGuard. + * Copyright (c) sk89q + * Copyright (c) the WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY), without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see . +*/ + +package com.sk89q.worldguard.region.attribute; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import org.apache.commons.lang.Validate; + +/** + * An attribute that stores three states (none, allow, and deny). + * + * @see Managed another way to store booleans (with more memory cost and null + * support) + */ +public class State extends Attribute { + + public static enum Type { + NONE, + ALLOW, + DENY + } + + private Type value = Type.NONE; + + /** + * Construct an instance and assign a default name. + * + * @see Attribute#Attribute() for basic mechanics + */ + public State() { + } + + /** + * Construct an instance and specify an attribute name. + * + * @param name name of the attribute + */ + public State(String name) { + super(name); + } + + /** + * Get the value. + * + * @return the value + */ + public Type getValue() { + return value; + } + + /** + * Set the value. + * + * @param value the new value + */ + public void setValue(Type value) { + Validate.notNull(value); + + this.value = value; + } + + /** + * Returns whether the value is {@link Type#NONE} or {@link Type#ALLOW}. + * + * @return whether the value is for allow + */ + public boolean allows() { + return value == Type.NONE || value == Type.ALLOW; + } + + @Override + public void read(DataInputStream in, int len) throws IOException { + int ordinal = in.readByte(); + Type[] values = Type.values(); + + if (ordinal < 0 || ordinal >= values.length) { + value = Type.NONE; + } else { + value = values[ordinal]; + } + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeByte(value.ordinal()); + } + + @Override + public String toString() { + return String.valueOf(value); + } + +} diff --git a/src/main/java/com/sk89q/worldguard/region/Stereotype.java b/src/main/java/com/sk89q/worldguard/region/attribute/Stereotype.java similarity index 98% rename from src/main/java/com/sk89q/worldguard/region/Stereotype.java rename to src/main/java/com/sk89q/worldguard/region/attribute/Stereotype.java index 8bb42b55..f679edf8 100644 --- a/src/main/java/com/sk89q/worldguard/region/Stereotype.java +++ b/src/main/java/com/sk89q/worldguard/region/attribute/Stereotype.java @@ -16,7 +16,7 @@ * this program. If not, see . */ -package com.sk89q.worldguard.region; +package com.sk89q.worldguard.region.attribute; import java.io.DataInputStream; import java.io.DataOutputStream; diff --git a/src/main/java/com/sk89q/worldguard/region/stores/YamlStore.java b/src/main/java/com/sk89q/worldguard/region/stores/YamlStore.java index ed236996..0f618acf 100644 --- a/src/main/java/com/sk89q/worldguard/region/stores/YamlStore.java +++ b/src/main/java/com/sk89q/worldguard/region/stores/YamlStore.java @@ -44,8 +44,6 @@ import com.sk89q.rebar.util.LoggerUtils; import com.sk89q.worldedit.BlockVector2D; import com.sk89q.worldedit.Vector; import com.sk89q.worldguard.region.Region; -import com.sk89q.worldguard.region.flags.DefaultFlag; -import com.sk89q.worldguard.region.flags.Flag; import com.sk89q.worldguard.region.indices.RegionIndex; import com.sk89q.worldguard.region.indices.RegionIndexFactory; import com.sk89q.worldguard.region.shapes.Cuboid; @@ -166,7 +164,8 @@ public class YamlStore implements RegionStore { Region region = new Region(id, shape); region.setPriority(node.getInt("priority", 0)); - loadFlags(region, node.getNode("flags")); + loadFlags(region, node.getNode("flags")); // Legacy flags support + loadAttributes(region, node.getNode("attr")); // Remember the parent so that it can be linked lateron String parentId = node.getString("parent"); @@ -272,6 +271,9 @@ public class YamlStore implements RegionStore { } } } + private void loadAttributes(Region region, ConfigurationNode node) { + + } /** * Try to set a flag on a region from its raw value. The flag will be properly diff --git a/src/test/java/com/sk89q/worldguard/region/AttributeTest.java b/src/test/java/com/sk89q/worldguard/region/AttributeTest.java index 2add403b..13401b27 100644 --- a/src/test/java/com/sk89q/worldguard/region/AttributeTest.java +++ b/src/test/java/com/sk89q/worldguard/region/AttributeTest.java @@ -28,6 +28,8 @@ import java.io.IOException; import org.junit.Test; +import com.sk89q.worldguard.region.attribute.Attribute; + public class AttributeTest { private Attribute makeAttribute(String name) { diff --git a/src/test/java/com/sk89q/worldguard/region/DataValuedAttributeTest.java b/src/test/java/com/sk89q/worldguard/region/DataValuedAttributeTest.java index ddd1703c..ae38c067 100644 --- a/src/test/java/com/sk89q/worldguard/region/DataValuedAttributeTest.java +++ b/src/test/java/com/sk89q/worldguard/region/DataValuedAttributeTest.java @@ -28,15 +28,17 @@ import java.io.IOException; import org.junit.Test; +import com.sk89q.worldguard.region.attribute.ByteArray; + public class DataValuedAttributeTest extends AttributeTest { - private DataValuedAttribute makeAttribute(String name) { - return new DataValuedAttribute(name); + private ByteArray makeAttribute(String name) { + return new ByteArray(name); } @Test public void testRead() throws IOException { - DataValuedAttribute attribute = makeAttribute("testing"); + ByteArray attribute = makeAttribute("testing"); byte[] data = new byte[] { 1, 2, 3, 4, 5, 6 }; attribute.read(new DataInputStream(new ByteArrayInputStream(data)), data.length); assertArrayEquals(data, attribute.getByteArray()); @@ -45,8 +47,8 @@ public class DataValuedAttributeTest extends AttributeTest { @Test public void testWrite() throws IOException { byte[] data = new byte[] { 1, 2, 3, 4, 5, 6 }; - DataValuedAttribute attribute = makeAttribute("testing"); - attribute.setByteArray(data); + ByteArray attribute = makeAttribute("testing"); + attribute.setValue(data); ByteArrayOutputStream stream = new ByteArrayOutputStream(100); attribute.write(new DataOutputStream(stream)); diff --git a/src/test/java/com/sk89q/worldguard/region/TestAttribute.java b/src/test/java/com/sk89q/worldguard/region/TestAttribute.java index 55befc67..3d718d3b 100644 --- a/src/test/java/com/sk89q/worldguard/region/TestAttribute.java +++ b/src/test/java/com/sk89q/worldguard/region/TestAttribute.java @@ -24,6 +24,8 @@ import java.io.IOException; import org.junit.Ignore; +import com.sk89q.worldguard.region.attribute.Attribute; + @Ignore public class TestAttribute extends Attribute {