String init()

in juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java [178:508]


		String init(BeanMeta<T> beanMeta) {
			Class<?> c = classMeta.getInnerClass();
			ClassInfo ci = classMeta.getInfo();

			try {
				Visibility
					conVis = ctx.getBeanConstructorVisibility(),
					cVis = ctx.getBeanClassVisibility(),
					mVis = ctx.getBeanMethodVisibility(),
					fVis = ctx.getBeanFieldVisibility();

				List<Class<?>> bdClasses = list();
				if (beanFilter != null && beanFilter.getBeanDictionary() != null)
					addAll(bdClasses, beanFilter.getBeanDictionary());

				Value<String> typeName = Value.empty();
				classMeta.forEachAnnotation(Bean.class, x -> isNotEmpty(x.typeName()), x -> typeName.set(x.typeName()));
				if (typeName.isPresent())
					bdClasses.add(classMeta.innerClass);
				this.beanRegistry = new BeanRegistry(ctx, null, bdClasses.toArray(new Class<?>[bdClasses.size()]));

				Value<String> typePropertyName = Value.empty();
				classMeta.forEachAnnotation(Bean.class, x -> isNotEmpty(x.typePropertyName()), x -> typePropertyName.set(x.typePropertyName()));
				this.typePropertyName = typePropertyName.orElseGet(()->ctx.getBeanTypePropertyName());

				fluentSetters = (ctx.isFindFluentSetters() || (beanFilter != null && beanFilter.isFluentSetters()));

				// If @Bean.interfaceClass is specified on the parent class, then we want
				// to use the properties defined on that class, not the subclass.
				Class<?> c2 = (beanFilter != null && beanFilter.getInterfaceClass() != null ? beanFilter.getInterfaceClass() : c);

				Class<?> stopClass = (beanFilter != null ? beanFilter.getStopClass() : Object.class);
				if (stopClass == null)
					stopClass = Object.class;

				Map<String,BeanPropertyMeta.Builder> normalProps = map();

				boolean hasBean = ci.hasAnnotation(ctx, Bean.class);
				boolean hasBeanIgnore = ci.hasAnnotation(ctx, BeanIgnore.class);

				/// See if this class matches one the patterns in the exclude-class list.
				if (ctx.isNotABean(c))
					return "Class matches exclude-class list";

				if (! hasBean && ! (cVis.isVisible(c.getModifiers()) || c.isAnonymousClass()))
					return "Class is not public";

				if (hasBeanIgnore)
					return "Class is annotated with @BeanIgnore";

				// Make sure it's serializable.
				if (beanFilter == null && ctx.isBeansRequireSerializable() && ! ci.isChildOf(Serializable.class))
					return "Class is not serializable";

				// Look for @Beanc constructor on public constructors.
				ci.forEachPublicConstructor(x -> x.hasAnnotation(ctx, Beanc.class), x -> {
					if (constructor != null)
						throw new BeanRuntimeException(c, "Multiple instances of '@Beanc' found.");
					constructor = x;
					constructorArgs = new String[0];
					ctx.forEachAnnotation(Beanc.class, x.inner(), y -> ! y.properties().isEmpty(), z -> constructorArgs = split(z.properties()));
					if (! x.hasNumParams(constructorArgs.length)) {
						if (constructorArgs.length != 0)
							throw new BeanRuntimeException(c, "Number of properties defined in '@Beanc' annotation does not match number of parameters in constructor.");
						constructorArgs = new String[x.getParamCount()];
						IntValue i = IntValue.create();
						x.forEachParam(null, pi -> {
							String pn = pi.getName();
							if (pn == null)
								throw new BeanRuntimeException(c, "Could not find name for parameter #{0} of constructor ''{1}''", i, x.getFullName());
							constructorArgs[i.getAndIncrement()] = pn;
						});
					}
					constructor.setAccessible();
				});

				// Look for @Beanc on all other constructors.
				if (constructor == null) {
					ci.forEachDeclaredConstructor(x -> x.hasAnnotation(ctx, Beanc.class), x -> {
						if (constructor != null)
							throw new BeanRuntimeException(c, "Multiple instances of '@Beanc' found.");
						constructor = x;
						constructorArgs = new String[0];
						ctx.forEachAnnotation(Beanc.class, x.inner(), y -> ! y.properties().isEmpty(), z -> constructorArgs = split(z.properties()));
						if (! x.hasNumParams(constructorArgs.length)) {
							if (constructorArgs.length != 0)
								throw new BeanRuntimeException(c, "Number of properties defined in '@Beanc' annotation does not match number of parameters in constructor.");
							constructorArgs = new String[x.getParamCount()];
							IntValue i = IntValue.create();
							x.forEachParam(null, y -> {
								String pn = y.getName();
								if (pn == null)
									throw new BeanRuntimeException(c, "Could not find name for parameter #{0} of constructor ''{1}''", i, x.getFullName());
								constructorArgs[i.getAndIncrement()] = pn;
							});
						}
						constructor.setAccessible();
					});
				}

				// If this is an interface, look for impl classes defined in the context.
				if (constructor == null)
					constructor = implClassConstructor;

				if (constructor == null)
					constructor = ci.getNoArgConstructor(hasBean ? Visibility.PRIVATE : conVis);

				if (constructor == null && beanFilter == null && ctx.isBeansRequireDefaultConstructor())
					return "Class does not have the required no-arg constructor";

				if (constructor != null)
					constructor.setAccessible();

				// Explicitly defined property names in @Bean annotation.
				Set<String> fixedBeanProps = set();
				Set<String> bpi = set();
				Set<String> bpx = set();
				Set<String> bpro = set();
				Set<String> bpwo = set();

				Set<String> filterProps = set();  // Names of properties defined in @Bean(properties)

				if (beanFilter != null) {

					Set<String> bfbpi = beanFilter.getProperties();

					filterProps.addAll(bfbpi);

					// Get the 'properties' attribute if specified.
					if (bpi.isEmpty())
						fixedBeanProps.addAll(bfbpi);

					if (beanFilter.getPropertyNamer() != null)
						propertyNamer = beanFilter.getPropertyNamer();

					bpro.addAll(beanFilter.getReadOnlyProperties());
					bpwo.addAll(beanFilter.getWriteOnlyProperties());
				}

				fixedBeanProps.addAll(bpi);

				if (propertyNamer == null)
					propertyNamer = ctx.getPropertyNamer();

				// First populate the properties with those specified in the bean annotation to
				// ensure that ordering first.
				fixedBeanProps.forEach(x -> normalProps.put(x, BeanPropertyMeta.builder(beanMeta, x)));

				if (ctx.isUseJavaBeanIntrospector()) {
					BeanInfo bi = null;
					if (! c2.isInterface())
						bi = Introspector.getBeanInfo(c2, stopClass);
					else
						bi = Introspector.getBeanInfo(c2, null);
					if (bi != null) {
						for (PropertyDescriptor pd : bi.getPropertyDescriptors()) {
							String name = pd.getName();
							if (! normalProps.containsKey(name))
								normalProps.put(name, BeanPropertyMeta.builder(beanMeta, name));
							normalProps.get(name).setGetter(pd.getReadMethod()).setSetter(pd.getWriteMethod());
						}
					}

				} else /* Use 'better' introspection */ {

					findBeanFields(ctx, c2, stopClass, fVis).forEach(x -> {
						String name = findPropertyName(x);
						if (name != null) {
							if (! normalProps.containsKey(name))
								normalProps.put(name, BeanPropertyMeta.builder(beanMeta, name));
							normalProps.get(name).setField(x);
						}
					});

					List<BeanMethod> bms = findBeanMethods(ctx, c2, stopClass, mVis, propertyNamer, fluentSetters);

					// Iterate through all the getters.
					bms.forEach(x -> {
						String pn = x.propertyName;
						Method m = x.method;
						if (! normalProps.containsKey(pn))
							normalProps.put(pn, new BeanPropertyMeta.Builder(beanMeta, pn));
						BeanPropertyMeta.Builder bpm = normalProps.get(pn);
						if (x.methodType == GETTER) {
							// Two getters.  Pick the best.
							if (bpm.getter != null) {

								if (! ctx.hasAnnotation(Beanp.class, m) && ctx.hasAnnotation(Beanp.class, bpm.getter))
									m = bpm.getter;  // @Beanp annotated method takes precedence.

								else if (m.getName().startsWith("is") && bpm.getter.getName().startsWith("get"))
									m = bpm.getter;  // getX() overrides isX().
							}
							bpm.setGetter(m);
						}
					});

					// Now iterate through all the setters.
					bms.forEach(x -> {
						if (x.methodType == SETTER) {
							BeanPropertyMeta.Builder bpm = normalProps.get(x.propertyName);
							if (x.matchesPropertyType(bpm))
								bpm.setSetter(x.method);
						}
					});

					// Now iterate through all the extraKeys.
					bms.forEach(x -> {
						if (x.methodType == EXTRAKEYS) {
							BeanPropertyMeta.Builder bpm = normalProps.get(x.propertyName);
							bpm.setExtraKeys(x.method);
						}
					});
				}

				typeVarImpls = map();
				findTypeVarImpls(c, typeVarImpls);
				if (typeVarImpls.isEmpty())
					typeVarImpls = null;

				// Eliminate invalid properties, and set the contents of getterProps and setterProps.
				for (Iterator<BeanPropertyMeta.Builder> i = normalProps.values().iterator(); i.hasNext();) {
					BeanPropertyMeta.Builder p = i.next();
					try {
						if (p.field == null)
							p.setInnerField(findInnerBeanField(ctx, c, stopClass, p.name));

						if (p.validate(ctx, beanRegistry, typeVarImpls, bpro, bpwo)) {

							if (p.getter != null)
								getterProps.put(p.getter, p.name);

							if (p.setter != null)
								setterProps.put(p.setter, p.name);

						} else {
							i.remove();
						}
					} catch (ClassNotFoundException e) {
						throw new BeanRuntimeException(c, e.getLocalizedMessage());
					}
				}

				// Check for missing properties.
				fixedBeanProps.forEach(x -> {
					if (! normalProps.containsKey(x))
						throw new BeanRuntimeException(c, "The property ''{0}'' was defined on the @Bean(properties=X) annotation of class ''{1}'' but was not found on the class definition.", x, ci.getSimpleName());
				});

				// Mark constructor arg properties.
				for (String fp : constructorArgs) {
					BeanPropertyMeta.Builder m = normalProps.get(fp);
					if (m == null)
						throw new BeanRuntimeException(c, "The property ''{0}'' was defined on the @Beanc(properties=X) annotation but was not found on the class definition.", fp);
					m.setAsConstructorArg();
				}

				// Make sure at least one property was found.
				if (beanFilter == null && ctx.isBeansRequireSomeProperties() && normalProps.isEmpty())
					return "No properties detected on bean class";

				sortProperties = (ctx.isSortProperties() || (beanFilter != null && beanFilter.isSortProperties())) && fixedBeanProps.isEmpty();

				properties = sortProperties ? sortedMap() : map();

				if (beanFilter != null && beanFilter.getTypeName() != null)
					dictionaryName = beanFilter.getTypeName();
				if (dictionaryName == null)
					dictionaryName = findDictionaryName(this.classMeta);

				normalProps.forEach((k,v) -> {
					BeanPropertyMeta pMeta = v.build();
					if (pMeta.isDyna())
						dynaProperty = pMeta;
					properties.put(k, pMeta);
				});

				// If a beanFilter is defined, look for inclusion and exclusion lists.
				if (beanFilter != null) {

					// Eliminated excluded properties if BeanFilter.excludeKeys is specified.
					Set<String> bfbpi = beanFilter.getProperties();
					Set<String> bfbpx = beanFilter.getExcludeProperties();

					if (bpi.isEmpty() && ! bfbpi.isEmpty()) {
						// Only include specified properties if BeanFilter.includeKeys is specified.
						// Note that the order must match includeKeys.
						Map<String,BeanPropertyMeta> properties2 = map();
						bfbpi.forEach(x -> {
							if (properties.containsKey(x))
								properties2.put(x, properties.remove(x));
						});
						hiddenProperties.putAll(properties);
						properties = properties2;
					}
					if (bpx.isEmpty() && ! bfbpx.isEmpty()) {
						bfbpx.forEach(x -> hiddenProperties.put(x, properties.remove(x)));
					}
				}

				if (! bpi.isEmpty()) {
					Map<String,BeanPropertyMeta> properties2 = map();
					bpi.forEach(x -> {
						if (properties.containsKey(x))
							properties2.put(x, properties.remove(x));
					});
					hiddenProperties.putAll(properties);
					properties = properties2;
				}

				bpx.forEach(x -> hiddenProperties.put(x, properties.remove(x)));

				if (pNames != null) {
					Map<String,BeanPropertyMeta> properties2 = map();
					for (String k : pNames) {
						if (properties.containsKey(k))
							properties2.put(k, properties.get(k));
						else
							hiddenProperties.put(k, properties.get(k));
					}
					properties = properties2;
				}

			} catch (BeanRuntimeException e) {
				throw e;
			} catch (Exception e) {
				return "Exception:  " + getStackTrace(e);
			}

			return null;
		}