| # Copyright 2026 The Chromium Authors | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """Java naming: class_name parsing, FeatureMap derivation, clash checks.""" | |
| import re | |
| from typing import Dict, List, Optional | |
| import models | |
| _JAVA_CLASS_NAME_RE = re.compile(r'^((?:[a-z_][A-Za-z0-9_]*\.)+)([A-Z]\w*)$') | |
| # Simple type names referenced by the generated code. A nested class with one | |
| # of these names would shadow it. | |
| _RESERVED_TYPE_NAMES = ('String', 'NullMarked') | |
| def parse_java_class_name(full_name: str, arg_name: str) -> models.JavaClass: | |
| m = _JAVA_CLASS_NAME_RE.match(full_name) | |
| if not m: | |
| raise models.GeneratorError( | |
| [ | |
| f'error: {arg_name} "{full_name}" is not a fully-qualified Java ' | |
| f'class name such as "org.chromium.foo.FooFeatures".' | |
| ] | |
| ) | |
| return models.JavaClass(package=m.group(1)[:-1], name=m.group(2)) | |
| def feature_map_for_source( | |
| source: models.ParsedSource, | |
| output_class: models.JavaClass, | |
| override: Optional[models.JavaClass], | |
| ) -> models.JavaClass: | |
| """Returns the FeatureMap class used to query the features of `source`. | |
| When `override` is set, it is returned without looking at `source`. | |
| """ | |
| if override: | |
| return override | |
| names = source.feature_map_names | |
| if not names: | |
| raise models.GeneratorError( | |
| [ | |
| f'{source.path}: error: Cannot determine the FeatureMap class: no ' | |
| f'JNI_<Name>_GetNativeMap function found. Please set ' | |
| f'feature_map_class_name in the java_flag_generator() target, e.g. ' | |
| f'feature_map_class_name = "{output_class.package}.FooFeatureMap".' | |
| ] | |
| ) | |
| if len(names) > 1: | |
| raise models.GeneratorError( | |
| [ | |
| f'{source.path}: error: Cannot determine the FeatureMap class: ' | |
| f'found several JNI_<Name>_GetNativeMap functions ' | |
| f'({", ".join(names)}). Set feature_map_class_name in the ' | |
| f'java_flag_generator() target, e.g. ' | |
| f'feature_map_class_name = "{output_class.package}.FooFeatureMap".' | |
| ] | |
| ) | |
| return models.JavaClass(output_class.package, names[0]) | |
| def nested_class_name(entry: models.FeatureEntry) -> str: | |
| """kFoo -> Foo. | |
| This Foo is always a valid Java identifier (see cpp_parser._ENTRY_RE). | |
| """ | |
| return entry.identifier[1:] | |
| def helper_method_name( | |
| feature_map: models.JavaClass, all_maps: List[models.JavaClass] | |
| ) -> str: | |
| if len(all_maps) == 1: | |
| return 'isFeatureEnabled' | |
| return f'isFeatureEnabledIn{feature_map.name}' | |
| def validate(features_class: models.FeaturesClass) -> None: | |
| errors = [] | |
| outer = features_class.java_class.name | |
| maps_by_simple_name: Dict[str, models.JavaClass] = {} | |
| for feature_map in features_class.feature_maps: | |
| other = maps_by_simple_name.setdefault(feature_map.name, feature_map) | |
| if other != feature_map: | |
| errors.append( | |
| f'error: FeatureMap classes {other.full_name} and ' | |
| f'{feature_map.full_name} have the same simple name.' | |
| ) | |
| if feature_map.name == outer: | |
| errors.append( | |
| f'error: The generated class {outer} has the same name as ' | |
| f'its FeatureMap class {feature_map.full_name}.' | |
| ) | |
| reserved = set(_RESERVED_TYPE_NAMES) | set(maps_by_simple_name) | |
| by_class_name: Dict[str, models.ResolvedFeature] = {} | |
| by_feature_name: Dict[str, models.ResolvedFeature] = {} | |
| for feature in features_class.features: | |
| entry = feature.entry | |
| name = feature.java_class_name | |
| where = f'{entry.source_path}:{entry.line}' | |
| if name == outer: | |
| errors.append( | |
| f'{where}: error: Nested class "{name}" would have the ' | |
| f'same name as the generated class.' | |
| ) | |
| elif name in reserved: | |
| errors.append( | |
| f'{where}: error: Nested class "{name}" would ' | |
| f'shadow a type used by the generated class.' | |
| ) | |
| first = by_class_name.setdefault(name, feature) | |
| if first is not feature: | |
| errors.append( | |
| f'{where}: error: "{entry.written}" and ' | |
| f'"{first.entry.written}" ({first.entry.source_path}:' | |
| f'{first.entry.line}) both map to the nested class ' | |
| f'"{name}".' | |
| ) | |
| continue | |
| first = by_feature_name.setdefault(feature.feature_name, feature) | |
| if first is not feature: | |
| errors.append( | |
| f'{where}: error: "{entry.written}" and ' | |
| f'"{first.entry.written}" ({first.entry.source_path}:' | |
| f'{first.entry.line}) both have the feature name ' | |
| f'"{feature.feature_name}".' | |
| ) | |
| if errors: | |
| raise models.GeneratorError(errors) |