// Path: observable-cache-service-processor-api/src/main/java/com/github/aleksandermielczarek/observablecache/service/processor/api/exception/ObservableCacheServiceException.java
// public class ObservableCacheServiceException extends RuntimeException {
// 
//     public ObservableCacheServiceException(String message) {
//         super(message);
//     }
// 
//     public ObservableCacheServiceException(String message, Throwable cause) {
//         super(message, cause);
// 
//     }
// 
// }
// 
// Path: observable-cache-service-processor-api/src/main/java/com/github/aleksandermielczarek/observablecache/service/processor/api/method/base/BaseMethod.java
// public interface BaseMethod extends WritableMethod {
// 
//     default boolean isBaseMethod(ExecutableElement method, ProcessingEnvironment processingEnvironment) {
//         if (!method.getParameters().isEmpty()) {
//             return false;
//         }
//         TypeElement returnedElement = (TypeElement) processingEnvironment.getTypeUtils().asElement(method.getReturnType());
//         if (!returnedElement.getQualifiedName().toString().contains(returnedType())) {
//             return false;
//         }
//         return true;
//     }
// 
//     default String keyName(ExecutableElement method) {
//         String camelCaseName = method.getSimpleName().toString();
//         return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, camelCaseName);
//     }
// 
//     default String keyValue(TypeElement type, ExecutableElement method) {
//         return type.getQualifiedName().toString() + "." + method.getSimpleName().toString();
//     }
// 
//     @Override
//     default void writeMethod(TypeSpec.Builder builder, ExecutableElement method, String keyName) {
//         builder.addMethod(MethodSpec.overriding(method)
//                 .addStatement("return observableCache." + cacheMethod() + "(" + keyName + ")")
//                 .build());
//     }
// 
//     String returnedType();
// 
//     String cacheMethod();
// }
// 
// Path: observable-cache-service-processor-api/src/main/java/com/github/aleksandermielczarek/observablecache/service/processor/api/method/extend/ExtendMethod.java
// public interface ExtendMethod extends WritableMethod {
// 
//     default boolean isExtendMethod(ExecutableElement baseMethodElement, BaseMethod baseMethod, ExecutableElement extendMethod, ProcessingEnvironment processingEnvironment) {
//         List<String> baseMethodTokens = nameTokens(baseMethodElement);
//         List<String> extendMethodTokens = nameTokens(extendMethod);
//         if (baseMethodTokens.size() >= extendMethodTokens.size()) {
//             return false;
//         }
//         boolean baseHasTokens = false;
//         for (int i = 0; i < extendMethodTokens.size(); i++) {
//             String token = extendMethodTokens.remove(i);
//             String lowerCaseToken = token.toLowerCase();
//             String newFirstItem = extendMethodTokens.get(0);
//             String newFirstItemLowerCase = newFirstItem.toLowerCase();
//             extendMethodTokens.set(0, newFirstItemLowerCase);
//             if (lowerCaseToken.equals(additionalToken()) && extendMethodTokens.equals(baseMethodTokens)) {
//                 baseHasTokens = true;
//             }
//             extendMethodTokens.set(0, newFirstItem);
//             extendMethodTokens.add(i, token);
//             if (baseHasTokens) {
//                 break;
//             }
//         }
//         return baseHasTokens;
//     }
// 
//     String additionalToken();
// 
// }


import com.github.aleksandermielczarek.observablecache.service.processor.api.exception.ObservableCacheServiceException;
import com.github.aleksandermielczarek.observablecache.service.processor.api.method.base.BaseMethod;
import com.github.aleksandermielczarek.observablecache.service.processor.api.method.extend.ExtendMethod;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
            if (baseMethodOptional.isPresent()) {
                BaseMethod baseMethod = baseMethodOptional.get();

                String keyValue = baseMethod.keyValue(observableCacheServiceInterfaceTypeElement, baseElement);
                String keyName = baseMethod.keyName(baseElement);
                baseMethod.writeMethod(builder, baseElement, keyName);
                builder.addField(FieldSpec.builder(String.class, keyName)
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
                        .initializer("$S", keyValue)
                        .build());

                Iterator<ExecutableElement> methodQueueIterator = methodsQueue.iterator();
                while (methodQueueIterator.hasNext()) {
                    ExecutableElement methodToImplement = methodQueueIterator.next();
                    extendMethods.stream()
                            .filter(extendMethod -> extendMethod.isExtendMethod(baseElement, baseMethod, methodToImplement, processingEnv))
                            .findFirst()
                            .ifPresent(extendMethod -> {
                                methodQueueIterator.remove();
                                extendMethod.writeMethod(builder, methodToImplement, keyName);
                            });
                }
            } else {
                methodsQueue.add(baseElement);
            }
            if (methodsQueue.isEmpty()) {
                break;
            }
        }
        if (!methodsQueue.isEmpty()) {
// Path: observable-cache-service-processor-api/src/main/java/com/github/aleksandermielczarek/observablecache/service/processor/api/exception/ObservableCacheServiceException.java
// public class ObservableCacheServiceException extends RuntimeException {
// 
//     public ObservableCacheServiceException(String message) {
//         super(message);
//     }
// 
//     public ObservableCacheServiceException(String message, Throwable cause) {
//         super(message, cause);
// 
//     }
// 
// }
// 
// Path: observable-cache-service-processor-api/src/main/java/com/github/aleksandermielczarek/observablecache/service/processor/api/method/base/BaseMethod.java
// public interface BaseMethod extends WritableMethod {
// 
//     default boolean isBaseMethod(ExecutableElement method, ProcessingEnvironment processingEnvironment) {
//         if (!method.getParameters().isEmpty()) {
//             return false;
//         }
//         TypeElement returnedElement = (TypeElement) processingEnvironment.getTypeUtils().asElement(method.getReturnType());
//         if (!returnedElement.getQualifiedName().toString().contains(returnedType())) {
//             return false;
//         }
//         return true;
//     }
// 
//     default String keyName(ExecutableElement method) {
//         String camelCaseName = method.getSimpleName().toString();
//         return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, camelCaseName);
//     }
// 
//     default String keyValue(TypeElement type, ExecutableElement method) {
//         return type.getQualifiedName().toString() + "." + method.getSimpleName().toString();
//     }
// 
//     @Override
//     default void writeMethod(TypeSpec.Builder builder, ExecutableElement method, String keyName) {
//         builder.addMethod(MethodSpec.overriding(method)
//                 .addStatement("return observableCache." + cacheMethod() + "(" + keyName + ")")
//                 .build());
//     }
// 
//     String returnedType();
// 
//     String cacheMethod();
// }
// 
// Path: observable-cache-service-processor-api/src/main/java/com/github/aleksandermielczarek/observablecache/service/processor/api/method/extend/ExtendMethod.java
// public interface ExtendMethod extends WritableMethod {
// 
//     default boolean isExtendMethod(ExecutableElement baseMethodElement, BaseMethod baseMethod, ExecutableElement extendMethod, ProcessingEnvironment processingEnvironment) {
//         List<String> baseMethodTokens = nameTokens(baseMethodElement);
//         List<String> extendMethodTokens = nameTokens(extendMethod);
//         if (baseMethodTokens.size() >= extendMethodTokens.size()) {
//             return false;
//         }
//         boolean baseHasTokens = false;
//         for (int i = 0; i < extendMethodTokens.size(); i++) {
//             String token = extendMethodTokens.remove(i);
//             String lowerCaseToken = token.toLowerCase();
//             String newFirstItem = extendMethodTokens.get(0);
//             String newFirstItemLowerCase = newFirstItem.toLowerCase();
//             extendMethodTokens.set(0, newFirstItemLowerCase);
//             if (lowerCaseToken.equals(additionalToken()) && extendMethodTokens.equals(baseMethodTokens)) {
//                 baseHasTokens = true;
//             }
//             extendMethodTokens.set(0, newFirstItem);
//             extendMethodTokens.add(i, token);
//             if (baseHasTokens) {
//                 break;
//             }
//         }
//         return baseHasTokens;
//     }
// 
//     String additionalToken();
// 
// }


// Path: observable-cache-service-processor-api/src/main/java/com/github/aleksandermielczarek/observablecache/service/processor/api/AbstractObservableCacheServiceProcessor.java
import com.github.aleksandermielczarek.observablecache.service.processor.api.exception.ObservableCacheServiceException;
import com.github.aleksandermielczarek.observablecache.service.processor.api.method.base.BaseMethod;
import com.github.aleksandermielczarek.observablecache.service.processor.api.method.extend.ExtendMethod;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;

            if (baseMethodOptional.isPresent()) {
                BaseMethod baseMethod = baseMethodOptional.get();

                String keyValue = baseMethod.keyValue(observableCacheServiceInterfaceTypeElement, baseElement);
                String keyName = baseMethod.keyName(baseElement);
                baseMethod.writeMethod(builder, baseElement, keyName);
                builder.addField(FieldSpec.builder(String.class, keyName)
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
                        .initializer("$S", keyValue)
                        .build());

                Iterator<ExecutableElement> methodQueueIterator = methodsQueue.iterator();
                while (methodQueueIterator.hasNext()) {
                    ExecutableElement methodToImplement = methodQueueIterator.next();
                    extendMethods.stream()
                            .filter(extendMethod -> extendMethod.isExtendMethod(baseElement, baseMethod, methodToImplement, processingEnv))
                            .findFirst()
                            .ifPresent(extendMethod -> {
                                methodQueueIterator.remove();
                                extendMethod.writeMethod(builder, methodToImplement, keyName);
                            });
                }
            } else {
                methodsQueue.add(baseElement);
            }
            if (methodsQueue.isEmpty()) {
                break;
            }
        }
        if (!methodsQueue.isEmpty()) {
            throw new ObservableCacheServiceException("Unable to implement methods for " + observableCacheServiceInterfaceTypeElement.getQualifiedName().toString());