Update of /cvsroot/xdoclet/xdoclet/modules/ejb/src/xdoclet/modules/ejb/env
In directory
sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv12138/modules/ejb/src/xdoclet/modules/ejb/env
Added Files:
EnvTagsHandler.java EnvEjbRefTagsHandler.java
Log Message:
added easier environment handling (XDT-1325)
--- NEW FILE: EnvTagsHandler.java ---
/*
* Copyright (c) 2001, 2002 The XDoclet team
* All rights reserved.
*/
package xdoclet.modules.ejb.env;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import xjavadoc.XField;
import xjavadoc.XMember;
import xjavadoc.XMethod;
import xjavadoc.XTag;
import xdoclet.XDocletException;
import xdoclet.XDocletTagSupport;
import xdoclet.util.TypeConversionUtil;
/**
* Handles field level tag's for configuring a bean's environment.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Matthias Germann</a>
* @created March 31, 2005
* @xdoclet.taghandler namespace="EjbEnv"
* @version $Revision: 1.1 $
*/
public class EnvTagsHandler extends XDocletTagSupport
{
/**
* The tags for which a value is bound in the java componenet environement
jndi namespace
*/
private final static String[] ENV_TAGS = {"ejb.env-entry",
"ejb.resource-ref", "ejb.resource-env-ref", "ejb.destination-ref",
"ejb.ejb-service-ref", "ejb.ejb-ref", "ejb.external-ref"};
/**
* Maps primitive types to their wrapper classes
*/
private final static Map wrappers;
protected XTag currentTag;
protected XMember currentMember;
protected int currentTagType;
static {
Map wps = new HashMap();
wps.put("boolean", "java.lang.Boolean");
wps.put("byte", "java.lang.Byte");
wps.put("char", "java.lang.Character");
wps.put("short", "java.lang.Short");
wps.put("int", "java.lang.Integer");
wps.put("float", "java.lang.Float");
wps.put("long", "java.lang.Long");
wps.put("double", "java.lang.Double");
wrappers = Collections.unmodifiableMap(wps);
}
/**
* Executes the template for all class-, method- and field-level tags with
the passed name
*
* @param template the template
* @param attributes the attributes
* @throws XDocletException if an error occures
* @doc.param name="tagName" optional="false"
description="the tag name"
* @doc.tag type="block"
*/
public void forAllTags(String template, Properties attributes) throws
XDocletException
{
forTags(template, attributes, true, true, true);
}
/**
* Executes the template for all method- and field-level tags with the
passed name
*
* @param template the template
* @param attributes the attributes
* @throws XDocletException if an error occures
* @doc.param name="tagName" optional="false"
description="the tag name"
* @doc.tag type="block"
*/
public void forAllMemberTags(String template, Properties attributes) throws
XDocletException
{
forTags(template, attributes, false, true, true);
}
/**
* Executes the template for all method-level tags with the passed name
*
* @param template the template
* @param attributes the attributes
* @throws XDocletException if an error occures
* @doc.param name="tagName" optional="false"
description="the tag name"
* @doc.tag type="block"
*/
public void forAllMethodTags(String template, Properties attributes) throws
XDocletException
{
forTags(template, attributes, false, true, false);
}
/**
* Returns the name parameter value for the current tag. If the name
parameter is not specified for a method- or
* field-level tag, the member's name is returned. If the name parameter is
not specified for a class level tag, an
* error is generated.
*
* @param attributes the attributes
* @return the name
* @exception XDocletException if an error occures
* @doc.param name="paramName" optional="false"
description="the name of the name parameter"
* @doc.tag type="content"
*/
public String name(Properties attributes) throws XDocletException
{
String paramName = attributes.getProperty("paramName");
if (paramName == null) {
throw new XDocletException("paramName attribute is mandatory");
}
String name = null;
StringTokenizer st = new StringTokenizer(paramName, ",");
while (name == null && st.hasMoreTokens()) {
name = currentTag.getAttributeValue(st.nextToken());
}
if (name == null) {
if (currentMember == null) {
// class level
mandatoryParamNotFound(currentTag.getDoc(), paramName,
currentTag.getName());
}
else {
// method or field level
name = currentMember.getName();
}
}
return name;
}
/**
* Returns the type parameter value for the current tag. Returns the field
type for field-level tags and the return
* value for method-level tags. For class-level tags, the value of the type
parameter is returned. The wrapper class
* is returned for primitive fields an methods with primitive return values.
*
* @param attributes the attributes
* @return the type
* @exception XDocletException if an error occures
* @doc.param name="paramName" optional="false"
description="the name of the type parameter"
* @doc.param name="values" description="The valid values
for the parameter, comma separated. An
* error message is printed if the parameter value is not one of the
values."
* @doc.param name="default" description="The default
value is returned if parameter not specified
* by user for the tag."
* @doc.param name="mandatory" values="true,false"
description="Generate an error if parameter not
* @doc.tag type="content"
*/
public String type(Properties attributes) throws XDocletException
{
if (currentMember == null) {
// class level tags
return paramValue(attributes);
}
else {
// method and field level tags
String type = currentMemberType();
String wrapper = (String) wrappers.get(type);
return wrapper == null ? type : wrapper;
}
}
/**
* Executes the body only if the current tag is either a method- or
fiel-level tag or has a type parameter.
*
* @param template the template
* @param attributes the attributes
* @exception XDocletException if an error occures
* @doc.param name="paramName" optional="false"
description="the name of the name parameter"
* @doc.tag type="block"
*/
public void ifHasType(String template, Properties attributes) throws
XDocletException
{
String paramName = attributes.getProperty("paramName");
if (paramName == null) {
throw new XDocletException("paramName attribute is mandatory");
}
if (currentMember == null) {
// class level tags
String type = currentTag.getAttributeValue(paramName);
if (type != null) {
generate(template);
}
}
else {
// method and field level tags
generate(template);
}
}
/**
* Returns the method or field name. Can only be used inside
<code>forAllMemberTags</code> or <code>forAllMethodTags</code>
* .
*
* @param attributes the attributes
* @return the memeber's name
* @exception XDocletException if an error occures
* @doc.param name="prefix" optional="true"
description="the prefix for the name"
* @doc.tag type="content"
*/
public String memberName(Properties attributes) throws XDocletException
{
if (currentMember == null) {
throw new XDocletException("XDtEjbEnv:memberName can only be used
inside forAllMemberTags or forAllMethodTags");
}
String name = currentMember.getName();
String prefix = attributes.getProperty("prefix");
if (prefix != null) {
name = prefix + "/" + name;
}
return name;
}
/**
* Returns the method's return type or the field's type. Can only be used
inside <code>forAllMemberTags</code> or
* <code>forAllMethodTags</code>.
*
* @return the member's type
* @exception XDocletException if an error occures
* @doc.tag type="content"
*/
public String memberType() throws XDocletException
{
if (currentMember == null) {
throw new XDocletException("XDtEjbEnv:memberType can only be used
inside forAllMemberTags or forAllMethodTags");
}
return currentMemberType();
}
/**
* Returns the method signature for the current method. Can only be used
inside <code>forAllMethodTags</code>.
*
* @return the current method's signature
* @exception XDocletException if an error occures
* @doc.tag type="content"
*/
public String methodSignature() throws XDocletException
{
if (currentMember == null || !(currentMember instanceof XMethod)) {
throw new XDocletException("XDtEjbEnv:methodSignature can only be
used inside forAllMemberTags or forAllMethodTags");
}
XMethod method = (XMethod) currentMember;
StringBuffer sb = new StringBuffer();
if (Modifier.isProtected(method.getModifierSpecifier())) {
sb.append("protected ");
}
if (Modifier.isPublic(method.getModifierSpecifier())) {
sb.append("public ");
}
sb.append(method.getReturnType().getType().getQualifiedName());
sb.append(' ');
sb.append(method.getNameWithSignature(true));
return sb.toString();
}
/**
* Returns the value of a parameter.
*
* @param attributes the attributes
* @return the value
* @exception XDocletException if an error occures
* @doc.param name="paramName" optional="false"
description="the name of the parameter"
* @doc.param name="values" description="The valid values
for the parameter, comma separated. An
* error message is printed if the parameter value is not one of the
values."
* @doc.param name="default" description="The default
value is returned if parameter not specified
* by user for the tag."
* @doc.param name="mandatory" values="true,false"
description="Generate an error if parameter not
* @doc.tag type="content"
*/
public String paramValue(Properties attributes) throws XDocletException
{
attributes.setProperty("tagName", currentTag.getName());
return getTagValue(attributes, currentTagType);
}
/**
* Executes the body only if the current tag has a specified parameter
*
* @param template the template
* @param attributes the attributes
* @exception XDocletException if an error occures
* @doc.param name="paramName" optional="false"
description="the name of the parameter"
* @doc.tag type="body"
*/
public void ifHasParam(String template, Properties attributes) throws
XDocletException
{
if (paramValue(attributes) != null) {
generate(template);
}
}
/**
* Executes the body only if the specified tag's value is equal to the
specified value
*
* @param template the template
* @param attributes the attributes
* @exception XDocletException if an error occures
* @doc.param name="paramName" optional="false"
description="the name of the parameter"
* @doc.param name="value" optional="false"
description="the value of the parameter"
* @doc.tag type="body"
*/
public void ifParamValueEquals(String template, Properties attributes)
throws XDocletException
{
if (isParamValueEqual(attributes)) {
generate(template);
}
}
/**
* Executes the body only if the specified tag's value is equal to the
specified value
*
* @param template the template
* @param attributes the attributes
* @exception XDocletException if an error occures
* @doc.param name="paramName" optional="false"
description="the name of the parameter"
* @doc.param name="value" optional="false"
description="the value of the parameter"
* @doc.tag type="body"
*/
public void ifParamValueNotEquals(String template, Properties attributes)
throws XDocletException
{
if (!isParamValueEqual(attributes)) {
generate(template);
}
}
/**
* Executes the body only if the current field type or method return type
is primitive.
*
* @param template the template
* @param attributes the attributes
* @exception XDocletException if an error occures
* @doc.tag type="block"
*/
public void ifPrimitiveMember(String template, Properties attributes)
throws XDocletException
{
if (currentMember != null) {
String type = currentMemberType();
String wrapper = (String) wrappers.get(type);
if (wrapper != null) {
generate(template);
}
}
}
/**
* Executes the body only if the current class has at least one field or
method tag for which a value is bound in
* the java componenet environement jndi namespace
*
* @param template the template
* @param attributes the attributes
* @exception XDocletException if an error occures
* @doc.tag type="block"
*/
public void ifHasEnvTags(String template, Properties attributes) throws
XDocletException
{
if (hasMemberWithEnvTag(getCurrentClass().getMethods(true))) {
generate(template);
}
else {
if (hasMemberWithEnvTag(getCurrentClass().getFields(true))) {
generate(template);
}
}
}
/**
* Executes the passed template for the passed
*
* @param template the template
* @param attributes the parameters
* @param forClass indicates whether the template should be
excuted for class level tags
* @param forMethod indicates whether the template should be
excuted for method level tags
* @param forField indicates whether the template should be
excuted for field level tags
* @throws XDocletException if an error occures
*/
protected void forTags(String template, Properties attributes, boolean
forClass, boolean forMethod, boolean forField) throws XDocletException
{
boolean superclasses =
TypeConversionUtil.stringToBoolean(attributes.getProperty("superclasses"),
true);
String tagName = attributes.getProperty("tagName");
if (tagName == null) {
throw new XDocletException("tagName is mandatory");
}
StringTokenizer st = new StringTokenizer(tagName, ",");
while (st.hasMoreTokens()) {
forTagsInternal(template, st.nextToken(), superclasses, forClass,
forMethod, forField);
}
}
/**
* Called for each tag in the <code>forTags</code> loop. The default
behaviour is to call <code>generate(template)</code>
*
* @param template the template
* @throws XDocletException if an error occures
*/
protected void doGenerate(String template) throws XDocletException
{
generate(template);
}
/**
* Returns whether the parameter's value is equal to the specfied value
*
* @param attributes the attributes
* @return <code>true</code> if it is equal
* @throws XDocletException if an error occures
*/
private boolean isParamValueEqual(Properties attributes) throws
XDocletException
{
String value = attributes.getProperty("value");
if (value == null) {
throw new XDocletException("value is mandatory");
}
return value.equals(paramValue(attributes));
}
/**
* Executes the passed template for the passed
*
* @param template the template
* @param tagName the tag-name
* @param superclasses indicates whether the superclasses of the
current class should also be searched
* @param forClass indicates whether the template should be
excuted for class level tags
* @param forMethod indicates whether the template should be
excuted for method level tags
* @param forField indicates whether the template should be
excuted for field level tags
* @throws XDocletException if an error occures
*/
private void forTagsInternal(String template, String tagName, boolean
superclasses, boolean forClass, boolean forMethod, boolean forField) throws
XDocletException
{
// class level tags
if (forClass) {
currentTagType = FOR_CLASS;
Collection tags = getCurrentClass().getDoc().getTags(tagName,
superclasses);
for (Iterator it = tags.iterator(); it.hasNext(); ) {
currentTag = (XTag) it.next();
setCurrentClassTag(currentTag);
currentMember = null;
doGenerate(template);
setCurrentClassTag(null);
}
}
// method level tags
if (forMethod) {
currentTagType = FOR_METHOD;
Collection methods = getCurrentClass().getMethods(superclasses);
for (Iterator it = methods.iterator(); it.hasNext(); ) {
XMethod method = (XMethod) it.next();
setCurrentMethod(method);
Collection tags = method.getDoc().getTags(tagName);
for (Iterator it2 = tags.iterator(); it2.hasNext(); ) {
currentTag = (XTag) it2.next();
setCurrentMethodTag(currentTag);
currentMember = method;
doGenerate(template);
setCurrentMethodTag(null);
}
setCurrentMethod(null);
}
}
// field level tags
if (forField) {
currentTagType = FOR_FIELD;
Collection fields = getCurrentClass().getFields(superclasses);
for (Iterator it = fields.iterator(); it.hasNext(); ) {
XField field = (XField) it.next();
setCurrentField(field);
Collection tags = field.getDoc().getTags(tagName);
for (Iterator it2 = tags.iterator(); it2.hasNext(); ) {
currentTag = (XTag) it2.next();
setCurrentFieldTag(currentTag);
currentMember = field;
doGenerate(template);
setCurrentFieldTag(null);
}
setCurrentField(null);
}
}
currentTagType = 0;
}
/**
* Returns whether the passed Collection of Members has at least one of the
tags for which a value is bound in the
* java componenet environement jndi namespace
*
* @param members a <code>Collection</code> o [EMAIL PROTECTED] XMember}
* @return <code>true</code> if the passed Collection of Members
has at least one of the tags for which a
* value is bound in the java componenet environement jndi namespace
*/
private boolean hasMemberWithEnvTag(Collection members)
{
for (Iterator it = members.iterator(); it.hasNext(); ) {
XMember member = (XMember) it.next();
for (int i = 0; i < ENV_TAGS.length; i++) {
Collection tags = member.getDoc().getTags(ENV_TAGS[i]);
if (tags.size() > 0) {
return true;
}
}
}
return false;
}
/**
* Returns the type of the current member
*
* @return the type
*/
private String currentMemberType()
{
if (currentMember instanceof XField) {
return ((XField) currentMember).getType().getQualifiedName();
}
else {
return ((XMethod)
currentMember).getReturnType().getType().getQualifiedName();
}
}
}
--- NEW FILE: EnvEjbRefTagsHandler.java ---
/*
* Copyright (c) 2001, 2002 The XDoclet team
* All rights reserved.
*/
package xdoclet.modules.ejb.env;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import xjavadoc.XClass;
import xjavadoc.XTag;
import xdoclet.XDocletException;
import xdoclet.modules.ejb.EjbTagsHandler;
import xdoclet.modules.ejb.XDocletModulesEjbMessages;
import xdoclet.modules.ejb.home.HomeTagsHandler;
import xdoclet.util.LogUtil;
import xdoclet.util.Translator;
/**
* @author Matthias Germann
* @created April 5, 2005
* @xdoclet.taghandler namespace="EjbEnvEjbRef"
* @version $Revision 1.1 $
* @todo refactor ejbRefId properly to account for ejb:bean -
it may not be needed anymore.
* @todo refactor storeReferringClassId properly to take
ejb:bean into account - may not be needed
* anymore.
*/
public class EnvEjbRefTagsHandler extends EnvTagsHandler
{
/**
* The id of the EJB referencing another EJB, used for setting up a correct
unique id for the ejb-ref.
*
* @see #ejbRefId()
* @see #forAllEjbRefs(java.lang.String,java.util.Properties)
* @see #storeReferringClassId()
*/
private String referringClassId;
private Map already = new HashMap();
private XClass refedEJBClass;
/**
* Returns unique id for the specified ejb-ref. It prefixes it with the
referring class's id, then a _ and the id of
* the ejb object.
*
* @return Description of the Returned Value
* @exception XDocletException
* @todo refactor this properly to account for
ejb:bean - it may not be needed anymore.
* @doc.tag type="content"
*/
public String ejbRefId() throws XDocletException
{
return referringClassId + '_' +
EjbTagsHandler.getEjbIdFor(refedEJBClass);
}
/**
* Evaluates the body block for each ejb:ejb-ref defined for the EJB. One
of the useful things is does is to lookup
* the EJB using the ejb-name parameter of ejb:ejb-ref and fill in other
required info.
*
* @param template The body of the block tag
* @param attributes The attributes of the template tag
* @exception XDocletException
* @doc.tag type="block"
* @doc.param name="tagName" description="the ejb-ref
tag" default="ejb.ejb-ref"
*/
public void forAllEjbRefs(String template, Properties attributes) throws
XDocletException
{
already.clear();
if (attributes.getProperty("tagName") == null) {
attributes.setProperty("tagName", "ejb.ejb-ref");
}
forTags(template, attributes, true, true, true);
already.clear();
}
/**
* Returns the global JNDI name for the current EJB ref.
*
* @return The JNDI name of current EJB ref.
* @exception XDocletException
* @doc.tag type="content"
*/
public String ejbRefJndiName() throws XDocletException
{
String ejbRefJndiName = null;
String jndiNameParameter = currentTag.getAttributeValue("jndi-name");
if (jndiNameParameter != null) {
ejbRefJndiName = jndiNameParameter;
}
else {
String refed_ejb_name = currentTag.getAttributeValue("ejb-name");
if (refed_ejb_name == null) {
throw new XDocletException("No ejb-name attribute found in
ejb-ref specified in bean " + getCurrentClass());
}
XClass refed_clazz = findEjb(refed_ejb_name);
String ejb_type = EjbTagsHandler.isLocalEjb(refed_clazz) ? "local"
: "remote";
ejbRefJndiName = HomeTagsHandler.getJndiNameOfTypeFor(ejb_type,
refed_clazz);
}
return ejbRefJndiName;
}
/**
* Generates code if the ejb-ref is local
*
* @param template
* @exception XDocletException
* @doc.tag type="block"
*/
public void ifLocalEjbRef(String template) throws XDocletException
{
if (isLocalEjbRef(currentTag)) {
generate(template);
}
}
/**
* Generates code if the ejb-ref is local
*
* @param template
* @exception XDocletException
* @doc.tag type="block"
*/
public void ifRemoteEjbRef(String template) throws XDocletException
{
if (isRemoteEjbRef(currentTag)) {
generate(template);
}
}
public String name(Properties attributes) throws XDocletException
{
if (currentMember == null) {
return EjbTagsHandler.ejbRefName(currentTag, refedEJBClass);
}
else {
attributes.setProperty("paramName", "ref-name");
return super.name(attributes);
}
}
/**
* Return true if the ejb-ref is local
*
* @param ejbRefTag
* @return true if the ejb-ref is local otherwise false
* @exception XDocletException
*/
protected boolean isLocalEjbRef(XTag ejbRefTag) throws XDocletException
{
String viewTypeParameter = ejbRefTag.getAttributeValue("view-type");
if (viewTypeParameter == null) {
return EjbTagsHandler.isLocalEjb(refedEJBClass) &&
!EjbTagsHandler.isRemoteEjb(refedEJBClass);
/*
* TODO introspection for fields and methods
* / use the memeber's type for field- and method-level tags
* XClass type;
* if (currentMember instanceof XMethod) {
* type = ((XMethod) currentMember).getReturnType().getType();
* }
* else {
* type = ((XField) currentMember).getType();
* }
* return type.isA("javax.ejb.EJBLocalHome") ||
type.isA("javax.ejb.EJBLocalObject");
*/
}
else {
return "local".equals(viewTypeParameter);
}
}
/**
* Return true if the ejb-ref is remote
*
* @param ejbRefTag
* @return true if the ejb-ref is remote otherwise
false
* @exception XDocletException
*/
protected boolean isRemoteEjbRef(XTag ejbRefTag) throws XDocletException
{
return !isLocalEjbRef(ejbRefTag);
}
/*
* (non-Javadoc)
* @see xdoclet.modules.ejb.env.EnvTagsHandler#doGenerate(java.lang.String)
*/
protected void doGenerate(String template) throws XDocletException
{
Log log = LogUtil.getLog(EnvEjbRefTagsHandler.class, "doGenerate");
storeReferringClassId();
String ejbNameAttribute = currentTag.getAttributeValue("ejb-name");
if ("ejb.ejb-ref".equals(currentTag.getName())) {
if (ejbNameAttribute == null || ejbNameAttribute.length() < 1) {
mandatoryParamNotFound(currentTag.getDoc(), "ejb-name",
"ejb.ejb-ref");
}
refedEJBClass = findEjb(ejbNameAttribute);
}
String refName = name(new Properties());
if (!already.containsKey(refName)) {
already.put(refName, currentTag);
if (refedEJBClass != null) {
pushCurrentClass(refedEJBClass);
}
generate(template);
if (refedEJBClass != null) {
popCurrentClass();
}
}
else {
XTag previousTag = (XTag) already.get(refName);
if
(!previousTag.getAttributeValue("ejb-name").equals(currentTag.getAttributeValue("ejb-name")))
{
log.error("Duplicate @ejb.ejb-ref found with different
parameters!");
log.error("Previous tag: @ejb.ejb-ref ref-name=\"" +
previousTag.getAttributeValue("ref-name") + "\"
ejb-name=\"" +
previousTag.getAttributeValue("ejb-name") + "\"
view-type=\"" +
previousTag.getAttributeValue("view-type") + "\"");
log.error("Current tag: @ejb.ejb-ref ref-name=\"" +
currentTag.getAttributeValue("ref-name") + "\" ejb-name=\""
+
currentTag.getAttributeValue("ejb-name") + "\"
view-type=\"" +
currentTag.getAttributeValue("view-type") + "\"");
throw new XDocletException("Duplicate @ejb.ejb-ref with
different parameters");
}
else {
log.warn("Duplicated @ejb.ejb-ref found, ref-name=\"" + refName
+ "\"");
}
}
referringClassId = null;
}
/**
* Stores the id of current EJB for further use by other tags in
referringClassId attribute.
*
* @exception XDocletException
* @todo refactor this properly to take ejb:bean
into account - may not be needed anymore.
*/
protected void storeReferringClassId() throws XDocletException
{
referringClassId = EjbTagsHandler.getEjbIdFor(getCurrentClass());
}
/**
* Finds and returns the class with the specified ejbName. An
XDocletException is thrown if not found.
*
* @param ejbName Description of Parameter
* @return Description of the Returned Value
* @exception XDocletException
*/
protected XClass findEjb(String ejbName) throws XDocletException
{
Collection classes = getXJavaDoc().getSourceClasses();
for (Iterator i = classes.iterator(); i.hasNext(); ) {
XClass clazz = (XClass) i.next();
if (EjbTagsHandler.isEjb(clazz) &&
ejbName.equals(EjbTagsHandler.getEjbNameFor(clazz))) {
return clazz;
}
}
throw new
XDocletException(Translator.getString(XDocletModulesEjbMessages.class,
XDocletModulesEjbMessages.NOT_DEFINED, new String[]{ejbName}));
}
}
-------------------------------------------------------
SF email is sponsored by - The IT Product Guide
Read honest & candid reviews on hundreds of IT Products from real users.
Discover which products truly live up to the hype. Start reading now.
http://ads.osdn.com/?ad_id=6595&alloc_id=14396&op=click
_______________________________________________
xdoclet-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/xdoclet-devel