Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions execution-flow-trace.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Execution Flow: `faker.name().fullName()`

```
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. faker.name() │
│ Faker.java:374-376 │
│ Returns the pre-instantiated Name object (created in constructor:142) │
└────────────────────────────────────┬────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 2. name.fullName() │
│ Name.java:53-55 │
│ Simply delegates to name() method │
└────────────────────────────────────┬────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 3. name.name() │
│ Name.java:28-30 │
│ Calls: faker.fakeValuesService().resolve("name.name", this, faker) │
└────────────────────────────────────┬────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 4. FakeValuesService.resolve("name.name", ...) │
│ FakeValuesService.java:304-312 │
│ - Calls safeFetch("name.name", null) to get expression from YAML │
│ - Returns one of these patterns (randomly chosen): │
│ "#{prefix} #{first_name} #{last_name}" │
│ "#{first_name} #{last_name} #{suffix}" │
│ "#{first_name} #{last_name}" (4 times - more likely) │
└────────────────────────────────────┬────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 5. FakeValuesService.resolveExpression(expression, current, root) │
│ FakeValuesService.java:343-367 │
│ - Uses regex EXPRESSION_PATTERN: #\{([a-z0-9A-Z_.]+)\s?((?:,?'([^']+)')*)│
│ - Finds each #{...} directive in the expression │
│ - For "#{first_name}": resolves to Name.firstName() method │
└────────────────────────────────────┬────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 6. Resolving #{first_name} directive │
│ FakeValuesService.java:380-421 │
│ Search order: │
│ a) Try method on current object (Name) → Name.firstName() ✓ │
│ b) If not found: try YAML key "name.first_name" │
│ c) If not found: try method on Faker root object │
│ d) If not found: try lowercase YAML lookup │
└────────────────────────────────────┬────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 7. Name.firstName() is invoked via reflection │
│ FakeValuesService.java:468-481 (resolveFromMethodOn) │
│ - Uses accessor() to find method ignoring case │
│ - Invokes method.invoke(obj, args) │
└────────────────────────────────────┬────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 8. Name.firstName() │
│ Name.java:61-63 │
│ Calls: faker.fakeValuesService().resolve("name.first_name", this, faker) │
└────────────────────────────────────┬────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 9. safeFetch("name.first_name") │
│ FakeValuesService.java:159-173 │
│ - Gets list from YAML: ["#{female_first_name}", "#{male_first_name}"] │
│ - Randomly selects one → e.g., "#{male_first_name}" │
└────────────────────────────────────┬────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 10. resolveExpression("#{male_first_name}", ...) │
│ - Looks up "name.male_first_name" in YAML │
│ - Gets array of 1000+ male names │
│ - RandomService.nextInt() picks one → e.g., "James" │
└────────────────────────────────────┬────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 11. Same process for #{last_name} │
│ - Name.lastName() → resolve("name.last_name") │
│ - safeFetch gets array of 400+ last names │
│ - RandomService picks one → e.g., "Smith" │
└────────────────────────────────────┬────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 12. Final result: "James Smith" │
│ Expression "#{first_name} #{last_name}" becomes "James Smith" │
└─────────────────────────────────────────────────────────────────────────────┘
```

## Key Components Summary

| Step | Class | Method | Purpose |
|------|-------|--------|---------|
| 1 | `Faker` | `name()` | Returns cached `Name` instance |
| 2-3 | `Name` | `fullName()` → `name()` | Delegates to resolve |
| 4-6 | `FakeValuesService` | `resolve()` → `resolveExpression()` | Parses `#{...}` expressions |
| 7 | `FakeValuesService` | `resolveFromMethodOn()` | Reflection-based method invocation |
| 8-11 | `FakeValuesService` | `safeFetch()` → `fetchObject()` | YAML data lookup + random selection |

## Data Flow

```
en/name.yml Code
───────────────────────────────── ──────────────────────────
name:
name: ← resolve("name.name")
- "#{first_name} #{last_name}"

first_name: ← resolve("name.first_name")
- "#{male_first_name}"
- "#{female_first_name}"

male_first_name: [James, ...] ← fetch("name.male_first_name")
last_name: [Smith, ...] ← fetch("name.last_name")
```

## Key Insight

The `FakeValuesService` recursively resolves `#{...}` expressions, which can reference either:

1. **Methods on the current object** (like `firstName()` on `Name`)
2. **Other YAML keys** (like `male_first_name`)
3. **Methods on `Faker` object** (like `regexify()`)
56 changes: 56 additions & 0 deletions overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Java Faker Codebase Overview

This is **Java Faker**, a data generation library for creating realistic fake data (names, addresses, phone numbers, etc.) for testing and development. It's a Java port of Ruby's Faker gem.

## Project Info
- **Version**: 1.0.3-SNAPSHOT
- **Build**: Maven (with wrapper)
- **Java**: 11+
- **License**: Apache 2.0

## Structure
```
src/main/java/com/github/javafaker/
├── Faker.java # Main entry point (facade)
├── *.java # 93 data provider classes
├── service/ # Core infrastructure
│ ├── FakeValuesService # Resolves YAML data, expressions
│ ├── FakeValues # Loads locale YAML files
│ └── RandomService # Random number generation
└── idnumbers/ # ID generators (SSN, Swedish IDs)

src/main/resources/
└── *.yml # 59 locale data files (en, de, fr, ja, etc.)

src/test/java/ # 111 test files
```

## Key Components

| Component | Purpose |
|-----------|---------|
| **Faker** | Central facade with 93+ provider instances |
| **FakeValuesService** | Resolves YAML expressions, handles locale chains |
| **RandomService** | Random numbers, hex, UUIDs |
| **Data Providers** | Name, Address, Internet, Company, Lorem, GameOfThrones, Pokemon, etc. |

## Dependencies
- **Guava** (33.5.0) - Utilities
- **Commons Lang3** (3.20.0) - String utils
- **SnakeYAML** (2.5) - YAML parsing
- **Generex** (1.0.2) - Regex-to-string generation

## How It Works
1. `Faker` instantiates all provider classes
2. Providers call `faker.fakeValuesService().resolve("key")`
3. `FakeValuesService` looks up data in locale YAML files
4. YAML expressions like `#{Name.first_name}` are evaluated dynamically
5. Results can be transformed with `numerify`, `letterify`, `regexify`

## Testing
- 111 test classes using JUnit 4 + Mockito
- Custom `@Repeat` annotation for multi-run tests
- Integration tests in `integration/` subdirectory
- JaCoCo coverage with Coveralls integration

The codebase is well-organized with clear separation between data providers and the service layer that handles data loading and expression evaluation.
2 changes: 1 addition & 1 deletion src/main/java/com/github/javafaker/Barcode.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

public class Barcode {

private Faker faker;
private final Faker faker;

public Barcode(Faker faker) {
this.faker = faker;
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/com/github/javafaker/Code.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public String isbn13(boolean separator) {
return separator ? isbn13.toString() : stripIsbnSeparator(isbn13);
}

private final int isbn10CheckDigit(CharSequence t) {
private int isbn10CheckDigit(CharSequence t) {
String value = stripIsbnSeparator(t);
int sum = 0;
for (int i = 0; i < value.length(); i++) {
Expand All @@ -128,7 +128,7 @@ private final int isbn10CheckDigit(CharSequence t) {
return sum % 11;
}

private final int isbn13CheckDigit(CharSequence t) {
private int isbn13CheckDigit(CharSequence t) {
String value = stripIsbnSeparator(t);
int sum = 0;
int multiplier = 0;
Expand All @@ -140,7 +140,7 @@ private final int isbn13CheckDigit(CharSequence t) {
return (10 - sum % 10) % 10;
}

private final String stripIsbnSeparator(CharSequence t) {
private String stripIsbnSeparator(CharSequence t) {
return t.toString().replaceAll("-","");
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/github/javafaker/Disease.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public String surgery() {
}

/**
* Generate random paediattics disease
* Generate random paediatrics disease
* @return A paediatrics disease
*/
public String paediatrics() {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/github/javafaker/Finance.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ private static int sum(String[] string) {
int sum = 0;
for (String s : string) {
if (!s.isEmpty()) {
sum += Integer.valueOf(s);
sum += Integer.parseInt(s);
}
}
return sum;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/github/javafaker/Internet.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ public String password(int minimumLength, int maximumLength, boolean includeUppe
*/
public String macAddress(String prefix) {
final String tmp = (prefix == null) ? "" : prefix;
final int prefixLength = tmp.trim().length() == 0
final int prefixLength = tmp.trim().isEmpty()
? 0
: tmp.split(":").length;

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/github/javafaker/Nation.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;

public class Nation {

private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
private static final Charset UTF8_CHARSET = StandardCharsets.UTF_8;

private final Faker faker;

Expand Down
6 changes: 1 addition & 5 deletions src/main/java/com/github/javafaker/PhoneNumber.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@ public String extension() {
}

public String subscriberNumber(int length) {
StringBuilder subscriberNumber = new StringBuilder();
for (int i = 0; i < length; i++) {
subscriberNumber.append("#");
}
return faker.numerify(subscriberNumber.toString());
return faker.numerify("#".repeat(Math.max(0, length)));
}

public String subscriberNumber() {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/github/javafaker/Sip.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public final class Sip {
private final Faker faker;
private final ArrayList<Integer> portPool;

protected Sip(final Faker faker) {
Sip(final Faker faker) {
this.faker = faker;
int port = 40000;
portPool = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

/**
* Implementation based on the definition at
* https://www.skatteverket.se/privat/folkbokforing/personnummer.4.3810a01c150939e893f18c29.html
* <a href="https://www.skatteverket.se/privat/folkbokforing/personnummer.4.3810a01c150939e893f18c29.html">...</a>
* and the description at
* https://en.wikipedia.org/wiki/Personal_identity_number_(Sweden)
* <a href="https://en.wikipedia.org/wiki/Personal_identity_number_(Sweden)">...</a>
*/
public class SvSEIdNumber {
private static final String[] validPatterns = {"######-####", "######+####"};
Expand Down
18 changes: 9 additions & 9 deletions src/main/java/com/github/javafaker/service/FakeValues.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ public class FakeValues implements FakeValuesInterface {
private final Locale locale;
private final String filename;
private final String path;
private Map values;
private Map<?,?> values;

FakeValues(Locale locale) {
this(locale, getFilename(locale), getFilename(locale));
}

private static String getFilename(Locale locale) {
final StringBuilder filename = new StringBuilder(language(locale));
if (!"".equals(locale.getCountry())) {
if (!locale.getCountry().isEmpty()) {
filename.append("-").append(locale.getCountry());
}
return filename.toString();
Expand All @@ -45,15 +45,15 @@ private static String language(Locale l) {
}

@Override
public Map get(String key) {
public Map<?,?> get(String key) {
if (values == null) {
values = loadValues();
}

return values == null ? null : (Map) values.get(key);
return values == null ? null : (Map<?,?>) values.get(key);
}

private Map loadValues() {
private Map<?,?> loadValues() {
String pathWithLocaleAndFilename = "/" + locale.getLanguage() + "/" + this.filename;
String pathWithFilename = "/" + filename + ".yml";
String pathWithLocale = "/" + locale.getLanguage() + ".yml";
Expand All @@ -71,17 +71,17 @@ private Map loadValues() {
return null;
}

final Map valuesMap = new Yaml().loadAs(stream, Map.class);
Map localeBased = (Map) valuesMap.get(locale.getLanguage());
final Map<?,?> valuesMap = new Yaml().loadAs(stream, Map.class);
Map<?,?> localeBased = (Map<?,?>) valuesMap.get(locale.getLanguage());
if (localeBased == null) {
localeBased = (Map) valuesMap.get(filename);
localeBased = (Map<?,?>) valuesMap.get(filename);
}
try {
stream.close();
} catch (IOException ex){
return null;
}
return (Map) localeBased.get("faker");
return (Map<?,?>) localeBased.get("faker");
}

private InputStream findStream(String filename) {
Expand Down
Loading